mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[Blocks Fixture] Drop the Blocks (#18837)
* [Blocks Fixture] Add a tiny router * Add a way to load nested entrypoint * Only expose URL params to nested routers * Add keys to route definitions * [Blocks Fixture] Drop the Blocks
This commit is contained in:
@@ -15,17 +15,16 @@ import React, {
|
||||
} from 'react';
|
||||
import {createCache, CacheProvider} from 'react/unstable-cache';
|
||||
import {RouterProvider} from './client/RouterContext';
|
||||
// TODO: entry point. This can't really done in the client code.
|
||||
import loadApp from './server/App.block';
|
||||
// TODO: can't really import a server component on the client.
|
||||
import App from './server/App';
|
||||
|
||||
const initialUrl = window.location.pathname;
|
||||
|
||||
const initialState = {
|
||||
// TODO: use this for invalidation.
|
||||
cache: createCache(),
|
||||
url: initialUrl,
|
||||
pendingUrl: initialUrl,
|
||||
RootBlock: loadApp(initialUrl),
|
||||
root: <App route={initialUrl} />,
|
||||
};
|
||||
|
||||
function reducer(state, action) {
|
||||
@@ -41,7 +40,7 @@ function reducer(state, action) {
|
||||
...state,
|
||||
url: action.url,
|
||||
pendingUrl: action.url,
|
||||
RootBlock: action.RootBlock,
|
||||
root: action.root,
|
||||
};
|
||||
default:
|
||||
throw new Error();
|
||||
@@ -65,7 +64,7 @@ function Router() {
|
||||
// TODO: Instant Transitions, somehow.
|
||||
dispatch({
|
||||
type: 'completeNavigation',
|
||||
RootBlock: loadApp(url),
|
||||
root: <App route={url} />,
|
||||
url,
|
||||
});
|
||||
});
|
||||
@@ -97,9 +96,7 @@ function Router() {
|
||||
return (
|
||||
<Suspense fallback={<h2>Loading...</h2>}>
|
||||
<CacheProvider value={state.cache}>
|
||||
<RouterProvider value={routeContext}>
|
||||
<state.RootBlock />
|
||||
</RouterProvider>
|
||||
<RouterProvider value={routeContext}>{state.root}</RouterProvider>
|
||||
</CacheProvider>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import {TabBar, TabLink} from '../client/TabNav';
|
||||
|
||||
export default function ProfileNav({userId}) {
|
||||
// TODO: Don't hardcode ID.
|
||||
return (
|
||||
<TabBar>
|
||||
<TabLink to={`/profile/${userId}`}>Timeline</TabLink>
|
||||
<TabLink to={`/profile/${userId}/bio`}>Bio</TabLink>
|
||||
</TabBar>
|
||||
);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {unstable_block as block} from 'react';
|
||||
|
||||
// Server
|
||||
|
||||
import loadFeedPage from './FeedPage.block';
|
||||
import loadProfilePage from './ProfilePage.block';
|
||||
|
||||
function load(url) {
|
||||
let Page;
|
||||
const segments = url.split('/').filter(Boolean);
|
||||
if (segments.length === 0) {
|
||||
Page = loadFeedPage();
|
||||
} else if (segments[0] === 'profile') {
|
||||
Page = loadProfilePage(Number(segments[1]), segments[2]);
|
||||
} else {
|
||||
throw Error('Not found');
|
||||
}
|
||||
return {Page};
|
||||
}
|
||||
|
||||
// Client
|
||||
|
||||
import Shell from '../client/Shell';
|
||||
|
||||
// TODO: some notion of key.
|
||||
|
||||
function App(props, data) {
|
||||
return (
|
||||
<Shell>
|
||||
<data.Page />
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
|
||||
export default block(App, load);
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {matchRoute} from './ServerRouter';
|
||||
import FeedPage from './FeedPage';
|
||||
import ProfilePage from './ProfilePage';
|
||||
|
||||
// TODO: Replace with asset reference.
|
||||
import loadShell from './Shell.block';
|
||||
|
||||
// TODO: Router component?
|
||||
const AppRoutes = {
|
||||
'/': props => <FeedPage {...props} key="home" />,
|
||||
'/profile/:userId/*': props => (
|
||||
<ProfilePage {...props} key={`profile-${props.userId}`} />
|
||||
),
|
||||
};
|
||||
|
||||
export default function App(props) {
|
||||
const Shell = loadShell();
|
||||
const match = matchRoute(props, AppRoutes);
|
||||
return <Shell>{match}</Shell>;
|
||||
}
|
||||
+6
-17
@@ -7,28 +7,19 @@
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {unstable_block as block} from 'react';
|
||||
|
||||
// Server
|
||||
|
||||
import {fetch} from 'react-data/fetch';
|
||||
|
||||
function load(postId) {
|
||||
return {
|
||||
comments: fetch(`/comments?postId=${postId}&_expand=user`).json(),
|
||||
};
|
||||
}
|
||||
// TODO: Replace with asset reference.
|
||||
import loadLink from './Link.block';
|
||||
|
||||
// Client
|
||||
|
||||
import Link from '../client/Link';
|
||||
|
||||
function Comments(props, data) {
|
||||
export default function Comments({postId}) {
|
||||
const Link = loadLink();
|
||||
const comments = fetch(`/comments?postId=${postId}&_expand=user`).json();
|
||||
return (
|
||||
<>
|
||||
<h5>Comments</h5>
|
||||
<ul>
|
||||
{data.comments.slice(0, 5).map(comment => (
|
||||
{comments.slice(0, 5).map(comment => (
|
||||
<li key={comment.id}>
|
||||
{comment.body}
|
||||
{' • '}
|
||||
@@ -39,5 +30,3 @@ function Comments(props, data) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default block(Comments, load);
|
||||
@@ -7,29 +7,15 @@
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {unstable_block as block} from 'react';
|
||||
import {fetch} from 'react-data/fetch';
|
||||
|
||||
// Server
|
||||
|
||||
import PostList from './PostList';
|
||||
|
||||
function load(params) {
|
||||
const allPosts = fetch('/posts?_expand=user').json();
|
||||
return {
|
||||
posts: <PostList posts={allPosts} />,
|
||||
};
|
||||
}
|
||||
|
||||
// Client
|
||||
|
||||
function FeedPage(props, data) {
|
||||
export default function Feed() {
|
||||
const posts = fetch('/posts?_expand=user').json();
|
||||
return (
|
||||
<>
|
||||
<h2>Feed</h2>
|
||||
{data.posts}
|
||||
<PostList posts={posts} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default block(FeedPage, load);
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {Suspense} from 'react';
|
||||
import PostGlimmer from './PostGlimmer';
|
||||
import Feed from './Feed';
|
||||
|
||||
export default function FeedPage() {
|
||||
return (
|
||||
<Suspense fallback={<PostGlimmer />}>
|
||||
<Feed />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {unstable_block as block} from 'react';
|
||||
|
||||
// Client
|
||||
|
||||
// TODO: delete this wrapper.
|
||||
|
||||
import ClientLink from '../client/Link';
|
||||
|
||||
function Link(props, _) {
|
||||
return <ClientLink {...props} />;
|
||||
}
|
||||
|
||||
export default block(Link);
|
||||
@@ -7,23 +7,14 @@
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {unstable_block as block, Suspense} from 'react';
|
||||
import {Suspense} from 'react';
|
||||
import Comments from './Comments';
|
||||
|
||||
// Server
|
||||
// TODO: Replace with asset reference.
|
||||
import loadLink from './Link.block';
|
||||
|
||||
import loadComments from './Comments.block';
|
||||
|
||||
function load(postId) {
|
||||
return {
|
||||
Comments: loadComments(postId),
|
||||
};
|
||||
}
|
||||
|
||||
// Client
|
||||
|
||||
import Link from '../client/Link';
|
||||
|
||||
function Post(props, data) {
|
||||
export default function Post({post}) {
|
||||
const Link = loadLink();
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -34,20 +25,16 @@ function Post(props, data) {
|
||||
maxWidth: 500,
|
||||
}}>
|
||||
<h4 style={{marginTop: 0}}>
|
||||
{props.post.title}
|
||||
{post.title}
|
||||
{' by '}
|
||||
<Link to={`/profile/${props.post.user.id}`}>
|
||||
{props.post.user.name}
|
||||
</Link>
|
||||
<Link to={`/profile/${post.user.id}`}>{post.user.name}</Link>
|
||||
</h4>
|
||||
<p>{props.post.body}</p>
|
||||
<p>{post.body}</p>
|
||||
<Suspense
|
||||
fallback={<h5>Loading comments...</h5>}
|
||||
unstable_avoidThisFallback={true}>
|
||||
<data.Comments />
|
||||
<Comments postId={post.id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default block(Post, load);
|
||||
@@ -9,16 +9,14 @@
|
||||
import * as React from 'react';
|
||||
import {Suspense, unstable_SuspenseList as SuspenseList} from 'react';
|
||||
import {preload} from 'react-data/fetch';
|
||||
|
||||
import loadPost from './Post.block';
|
||||
import PostGlimmer from './PostGlimmer';
|
||||
import Post from './Post';
|
||||
|
||||
export default function PostList({posts}) {
|
||||
return (
|
||||
<SuspenseList revealOrder="forwards" tail="collapsed">
|
||||
{posts.map(post => {
|
||||
preload(`/comments?postId=${post.id}&_expand=user`);
|
||||
const Post = loadPost(post.id);
|
||||
return (
|
||||
<Suspense key={post.id} fallback={<PostGlimmer />}>
|
||||
<Post post={post} />
|
||||
|
||||
+5
-18
@@ -7,28 +7,15 @@
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {unstable_block as block} from 'react';
|
||||
|
||||
// Server
|
||||
|
||||
import {fetch} from 'react-data/fetch';
|
||||
|
||||
function load(user) {
|
||||
return {
|
||||
user,
|
||||
bio: fetch(`/bios/${user.bioId}`).json(),
|
||||
};
|
||||
}
|
||||
|
||||
// Client
|
||||
|
||||
function ProfileBio(props, data) {
|
||||
export default function ProfileBio({userId}) {
|
||||
const user = fetch(`/users/${userId}`).json();
|
||||
const bio = fetch(`/bios/${user.bioId}`).json().text;
|
||||
return (
|
||||
<>
|
||||
<h3>{data.user.name}'s Bio</h3>
|
||||
<p>{data.bio.text}</p>
|
||||
<h3>{user.name}'s Bio</h3>
|
||||
<p>{bio}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default block(ProfileBio, load);
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {unstable_block as block} from 'react';
|
||||
|
||||
// Client
|
||||
|
||||
// TODO: delete this wrapper.
|
||||
|
||||
import ClientProfileNav from '../client/ProfileNav';
|
||||
|
||||
function ProfileNav(props, _) {
|
||||
return <ClientProfileNav {...props} />;
|
||||
}
|
||||
|
||||
export default block(ProfileNav);
|
||||
@@ -1,61 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {unstable_block as block, Suspense} from 'react';
|
||||
|
||||
// Server
|
||||
|
||||
import {fetch} from 'react-data/fetch';
|
||||
import loadProfileBio from './ProfileBio.block';
|
||||
import loadProfileTimeline from './ProfileTimeline.block';
|
||||
|
||||
function load(userId, tab) {
|
||||
const user = fetch(`/users/${userId}`).json();
|
||||
let Tab;
|
||||
switch (tab) {
|
||||
case 'bio':
|
||||
Tab = loadProfileBio(user);
|
||||
break;
|
||||
default:
|
||||
Tab = loadProfileTimeline(userId);
|
||||
break;
|
||||
}
|
||||
return {
|
||||
Tab,
|
||||
user,
|
||||
};
|
||||
}
|
||||
|
||||
// Client
|
||||
|
||||
import {TabBar, TabLink} from '../client/TabNav';
|
||||
|
||||
function ProfileTabNav({userId}) {
|
||||
// TODO: Don't hardcode ID.
|
||||
return (
|
||||
<TabBar>
|
||||
<TabLink to={`/profile/${userId}`}>Timeline</TabLink>
|
||||
<TabLink to={`/profile/${userId}/bio`}>Bio</TabLink>
|
||||
</TabBar>
|
||||
);
|
||||
}
|
||||
|
||||
function ProfilePage(props, data) {
|
||||
return (
|
||||
<>
|
||||
<h2>{data.user.name}</h2>
|
||||
<ProfileTabNav userId={data.user.id} />
|
||||
<Suspense fallback={<h3>Loading...</h3>}>
|
||||
<data.Tab />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default block(ProfilePage, load);
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Suspense} from 'react';
|
||||
import {fetch} from 'react-data/fetch';
|
||||
import {matchRoute} from './ServerRouter';
|
||||
import ProfileTimeline from './ProfileTimeline';
|
||||
import ProfileBio from './ProfileBio';
|
||||
|
||||
// TODO: Replace with asset reference.
|
||||
import loadProfileNav from './ProfileNav.block';
|
||||
|
||||
// TODO: Router component?
|
||||
const ProfileRoutes = {
|
||||
'/': props => <ProfileTimeline {...props} key="timeline" />,
|
||||
'/bio': props => <ProfileBio {...props} key="bio" />,
|
||||
};
|
||||
|
||||
export default function ProfilePage(props) {
|
||||
const user = fetch(`/users/${props.userId}`).json();
|
||||
const ProfileNav = loadProfileNav();
|
||||
const match = matchRoute(props, ProfileRoutes);
|
||||
return (
|
||||
<>
|
||||
<h2>{user.name}</h2>
|
||||
<ProfileNav userId={user.id} />
|
||||
<Suspense fallback={<h3>Loading...</h3>}>{match}</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {unstable_block as block} from 'react';
|
||||
|
||||
// Server
|
||||
|
||||
import {fetch} from 'react-data/fetch';
|
||||
import PostList from './PostList';
|
||||
|
||||
function load(userId) {
|
||||
const postsByUser = fetch(`/posts?userId=${userId}&_expand=user`).json();
|
||||
return {
|
||||
posts: <PostList posts={postsByUser} />,
|
||||
};
|
||||
}
|
||||
|
||||
// Client
|
||||
|
||||
function ProfileTimeline(props, data) {
|
||||
return <>{data.posts}</>;
|
||||
}
|
||||
|
||||
export default block(ProfileTimeline, load);
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {fetch} from 'react-data/fetch';
|
||||
import PostList from './PostList';
|
||||
|
||||
export default function ProfileTimeline({userId}) {
|
||||
const posts = fetch(`/posts?userId=${userId}&_expand=user`).json();
|
||||
return <PostList posts={posts} />;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
function tryMatch(props, def) {
|
||||
const defSegments = def.split('/').filter(Boolean);
|
||||
const routeSegments = props.route.split('/').filter(Boolean);
|
||||
let innerProps = {...props};
|
||||
while (routeSegments.length > 0) {
|
||||
if (defSegments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const urlSegment = routeSegments.shift();
|
||||
const defSegment = defSegments.shift();
|
||||
if (urlSegment === defSegment) {
|
||||
continue;
|
||||
}
|
||||
if (defSegment[0] === ':') {
|
||||
innerProps[defSegment.slice(1)] = urlSegment;
|
||||
continue;
|
||||
}
|
||||
if (defSegment === '*') {
|
||||
innerProps.route = '/' + urlSegment + routeSegments.join('/');
|
||||
return innerProps;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (defSegments.length === 0) {
|
||||
return innerProps;
|
||||
}
|
||||
if (defSegments.length === 1 && defSegments[0] === '*') {
|
||||
innerProps.route = '/';
|
||||
return innerProps;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function matchRoute(props, defs) {
|
||||
for (let def in defs) {
|
||||
if (!defs.hasOwnProperty(def)) {
|
||||
continue;
|
||||
}
|
||||
const innerProps = tryMatch(props, def);
|
||||
if (innerProps) {
|
||||
const match = defs[def](innerProps);
|
||||
return match;
|
||||
}
|
||||
}
|
||||
throw Error('Not found.');
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {unstable_block as block} from 'react';
|
||||
|
||||
// Client
|
||||
|
||||
// TODO: delete this wrapper.
|
||||
|
||||
import ClientShell from '../client/Shell';
|
||||
|
||||
function Shell(props, _) {
|
||||
return <ClientShell {...props} />;
|
||||
}
|
||||
|
||||
export default block(Shell);
|
||||
Reference in New Issue
Block a user