@samitouri / QOS-React-2 / commits / 4d124a4f67

[Blocks Fixture] Misc updates (#18811)

* [Blocks Fixture] Update navigation buttons immediately * Add more profile links * Minor refactor * Add subroutes to Profile

Dan Abramov committed May 5, 2020 at 11:53 UTC 4d124a4f67d9db6c6e97beab0dd8d7ae8a9177dd
13 files changed +224 -84
fixtures/blocks/db.json
+26 -8
@@ -23,37 +23,55 @@
23 {
24 "id": 1,
25 "body": "Hey there",
26 - "postId": 1
26 + "postId": 1,
27 + "userId": 1
28 },
29 {
30 "id": 2,
31 "body": "Welcome to the chat",
31 - "postId": 1
32 + "postId": 1,
33 + "userId": 2
34 },
35 {
36 "id": 3,
37 "body": "What editor/font are you using?",
36 - "postId": 2
38 + "postId": 2,
39 + "userId": 2
40 },
41 {
42 "id": 4,
43 "body": "It's always been hard",
41 - "postId": 3
44 + "postId": 3,
45 + "userId": 1
46 },
47 {
48 "id": 5,
49 "body": "It's still easy",
46 - "postId": 3
50 + "postId": 3,
51 + "userId": 2
52 }
53 ],
54 "users": [{
55 "id": 1,
51 - "name": "Sebastian"
56 + "name": "Sebastian",
57 + "bioId": 10
58 }, {
59 "id": 2,
54 - "name": "Sophie"
60 + "name": "Sophie",
61 + "bioId": 20
62 }, {
63 "id": 3,
57 - "name": "Dan"
64 + "name": "Dan",
65 + "bioId": 30
66 + }],
67 + "bios": [{
68 + "id": 10,
69 + "text": "I like European movies"
70 + }, {
71 + "id": 20,
72 + "text": "I like math puzzles"
73 + }, {
74 + "id": 30,
75 + "text": "I like reading twitter"
76 }]
77 }
fixtures/blocks/src/Router.js
+19 -7
@@ -24,17 +24,24 @@ const initialState = {
24 // TODO: use this for invalidation.
25 cache: createCache(),
26 url: initialUrl,
27 + pendingUrl: initialUrl,
28 RootBlock: loadApp(initialUrl),
29 };
30
31 function reducer(state, action) {
32 switch (action.type) {
32 - case 'navigate':
33 + case 'startNavigation':
34 + return {
35 + ...state,
36 + pendingUrl: action.url,
37 + };
38 + case 'completeNavigation':
39 // TODO: cancel previous fetch?
40 return {
35 - cache: state.cache,
41 + ...state,
42 url: action.url,
37 - RootBlock: loadApp(action.url),
43 + pendingUrl: action.url,
44 + RootBlock: action.RootBlock,
45 };
46 default:
47 throw new Error();
@@ -44,7 +51,7 @@ function reducer(state, action) {
51 function Router() {
52 const [state, dispatch] = useReducer(reducer, initialState);
53 const [startTransition, isPending] = useTransition({
47 - timeoutMs: 3000,
54 + timeoutMs: 1500,
55 });
56
57 useEffect(() => {
@@ -56,12 +63,16 @@ function Router() {
63 startTransition(() => {
64 // TODO: Here, There, and Everywhere.
65 // TODO: Instant Transitions, somehow.
59 - // TODO: Buttons should update immediately.
66 dispatch({
61 - type: 'navigate',
67 + type: 'completeNavigation',
68 + RootBlock: loadApp(url),
69 url,
70 });
71 });
72 + dispatch({
73 + type: 'startNavigation',
74 + url,
75 + });
76 },
77 [startTransition]
78 );
@@ -76,10 +87,11 @@ function Router() {
87
88 const routeContext = useMemo(
89 () => ({
90 + pendingUrl: state.pendingUrl,
91 url: state.url,
92 navigate,
93 }),
82 - [state.url, navigate]
94 + [state.url, state.pendingUrl, navigate]
95 );
96
97 return (
fixtures/blocks/src/client/Link.js new
+25
@@ -0,0 +1,25 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import * as React from 'react';
9 +import {useRouter} from './RouterContext';
10 +
11 +export default function Link({to, children, ...rest}) {
12 + const {navigate} = useRouter();
13 + return (
14 + <a
15 + href={to}
16 + onClick={e => {
17 + e.preventDefault();
18 + window.history.pushState(null, null, to);
19 + navigate(to);
20 + }}
21 + {...rest}>
22 + {children}
23 + </a>
24 + );
25 +}
fixtures/blocks/src/client/Shell.js
+10 -42
@@ -6,57 +6,25 @@
6 */
7
8 import * as React from 'react';
9 -import {useRouter} from './RouterContext';
9 +import {TabBar, TabLink} from './TabNav';
10
11 -function TabBar({children}) {
12 - return (
13 - <div style={{border: '1px solid #aaa', padding: 20, width: 500}}>
14 - {children}
15 - </div>
16 - );
17 -}
11 +// TODO: Error Boundaries.
12
19 -function TabLink({to, children}) {
20 - const {url: activeUrl, navigate} = useRouter();
21 - const active = activeUrl === to;
22 - if (active) {
23 - return (
24 - <b
25 - style={{
26 - display: 'inline-block',
27 - width: 50,
28 - marginRight: 20,
29 - }}>
30 - {children}
31 - </b>
32 - );
33 - }
13 +function MainTabNav() {
14 return (
35 - <a
36 - style={{
37 - display: 'inline-block',
38 - width: 50,
39 - marginRight: 20,
40 - }}
41 - href={to}
42 - onClick={e => {
43 - e.preventDefault();
44 - window.history.pushState(null, null, to);
45 - navigate(to);
46 - }}>
47 - {children}
48 - </a>
15 + <TabBar>
16 + <TabLink to="/">Home</TabLink>
17 + <TabLink to="/profile/3" partial={true}>
18 + Profile
19 + </TabLink>
20 + </TabBar>
21 );
22 }
23
24 export default function Shell({children}) {
25 return (
26 <>
55 - <TabBar>
56 - <TabLink to="/">Home</TabLink>
57 - <TabLink to="/profile">Profile</TabLink>
58 - </TabBar>
59 - <br />
27 + <MainTabNav />
28 {children}
29 </>
30 );
fixtures/blocks/src/client/TabNav.js new
+50
@@ -0,0 +1,50 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import * as React from 'react';
9 +import Link from './Link';
10 +import {useRouter} from './RouterContext';
11 +
12 +export function TabBar({children}) {
13 + return (
14 + <div
15 + style={{
16 + border: '1px solid #aaa',
17 + padding: 20,
18 + marginBottom: 20,
19 + width: 500,
20 + }}>
21 + {children}
22 + </div>
23 + );
24 +}
25 +
26 +export function TabLink({to, partial, children}) {
27 + const {pendingUrl: activeUrl} = useRouter();
28 + const active = partial ? activeUrl.startsWith(to) : activeUrl === to;
29 + if (active) {
30 + return (
31 + <b
32 + style={{
33 + display: 'inline-block',
34 + marginRight: 20,
35 + }}>
36 + {children}
37 + </b>
38 + );
39 + }
40 + return (
41 + <Link
42 + style={{
43 + display: 'inline-block',
44 + marginRight: 20,
45 + }}
46 + to={to}>
47 + {children}
48 + </Link>
49 + );
50 +}
fixtures/blocks/src/server/App.block.js
+9 -9
@@ -16,15 +16,13 @@ import loadProfilePage from './ProfilePage.block';
16
17 function load(url) {
18 let Page;
19 - switch (url) {
20 - case '/':
21 - Page = loadFeedPage();
22 - break;
23 - case '/profile':
24 - Page = loadProfilePage(3);
25 - break;
26 - default:
27 - throw Error('Not found');
19 + const segments = url.split('/').filter(Boolean);
20 + if (segments.length === 0) {
21 + Page = loadFeedPage();
22 + } else if (segments[0] === 'profile') {
23 + Page = loadProfilePage(Number(segments[1]), segments[2]);
24 + } else {
25 + throw Error('Not found');
26 }
27 return {Page};
28 }
@@ -33,6 +31,8 @@ function load(url) {
31
32 import Shell from '../client/Shell';
33
34 +// TODO: some notion of key.
35 +
36 function App(props, data) {
37 return (
38 <Shell>
fixtures/blocks/src/server/Comments.block.js
+9 -3
@@ -15,19 +15,25 @@ import {fetch} from 'react-data/fetch';
15
16 function load(postId) {
17 return {
18 - comments: fetch(`/comments?postId=${postId}`).json(),
18 + comments: fetch(`/comments?postId=${postId}&_expand=user`).json(),
19 };
20 }
21
22 // Client
23
24 +import Link from '../client/Link';
25 +
26 function Comments(props, data) {
27 return (
28 <>
29 <h5>Comments</h5>
30 <ul>
29 - {data.comments.slice(0, 5).map(item => (
30 - <li key={item.id}>{item.body}</li>
31 + {data.comments.slice(0, 5).map(comment => (
32 + <li key={comment.id}>
33 + {comment.body}
34 + {' • '}
35 + <Link to={`/profile/${comment.user.id}`}>{comment.user.name}</Link>
36 + </li>
37 ))}
38 </ul>
39 </>
fixtures/blocks/src/server/FeedPage.block.js
+1 -1
@@ -15,7 +15,7 @@ import {fetch} from 'react-data/fetch';
15 import PostList from './PostList';
16
17 function load(params) {
18 - const allPosts = fetch('/posts').json();
18 + const allPosts = fetch('/posts?_expand=user').json();
19 return {
20 posts: <PostList posts={allPosts} />,
21 };
fixtures/blocks/src/server/Post.block.js
+9 -1
@@ -21,6 +21,8 @@ function load(postId) {
21
22 // Client
23
24 +import Link from '../client/Link';
25 +
26 function Post(props, data) {
27 return (
28 <div
@@ -31,7 +33,13 @@ function Post(props, data) {
33 padding: 20,
34 maxWidth: 500,
35 }}>
34 - <h4 style={{marginTop: 0}}>{props.post.title}</h4>
36 + <h4 style={{marginTop: 0}}>
37 + {props.post.title}
38 + {' by '}
39 + <Link to={`/profile/${props.post.user.id}`}>
40 + {props.post.user.name}
41 + </Link>
42 + </h4>
43 <p>{props.post.body}</p>
44 <Suspense
45 fallback={<h5>Loading comments...</h5>}
fixtures/blocks/src/server/PostList.js
+1 -1
@@ -17,7 +17,7 @@ export default function PostList({posts}) {
17 return (
18 <SuspenseList revealOrder="forwards" tail="collapsed">
19 {posts.map(post => {
20 - preload(`/comments?postId=${post.id}`);
20 + preload(`/comments?postId=${post.id}&_expand=user`);
21 const Post = loadPost(post.id);
22 return (
23 <Suspense key={post.id} fallback={<PostGlimmer />}>
fixtures/blocks/src/server/ProfileBio.block.js new
+34
@@ -0,0 +1,34 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +/* eslint-disable import/first */
8 +
9 +import * as React from 'react';
10 +import {block} from 'react';
11 +
12 +// Server
13 +
14 +import {fetch} from 'react-data/fetch';
15 +
16 +function load(user) {
17 + return {
18 + user,
19 + bio: fetch(`/bios/${user.bioId}`).json(),
20 + };
21 +}
22 +
23 +// Client
24 +
25 +function ProfileBio(props, data) {
26 + return (
27 + <>
28 + <h3>{data.user.name}'s Bio</h3>
29 + <p>{data.bio.text}</p>
30 + </>
31 + );
32 +}
33 +
34 +export default block(ProfileBio, load);
fixtures/blocks/src/server/ProfilePage.block.js
+29 -5
@@ -12,23 +12,47 @@ import {block, Suspense} from 'react';
12 // Server
13
14 import {fetch} from 'react-data/fetch';
15 +import loadProfileBio from './ProfileBio.block';
16 import loadProfileTimeline from './ProfileTimeline.block';
17
17 -function load(userId) {
18 +function load(userId, tab) {
19 + const user = fetch(`/users/${userId}`).json();
20 + let Tab;
21 + switch (tab) {
22 + case 'bio':
23 + Tab = loadProfileBio(user);
24 + break;
25 + default:
26 + Tab = loadProfileTimeline(userId);
27 + break;
28 + }
29 return {
19 - user: fetch(`/users/${userId}`).json(),
20 - ProfileTimeline: loadProfileTimeline(userId),
30 + Tab,
31 + user,
32 };
33 }
34
35 // Client
36
37 +import {TabBar, TabLink} from '../client/TabNav';
38 +
39 +function ProfileTabNav({userId}) {
40 + // TODO: Don't hardcode ID.
41 + return (
42 + <TabBar>
43 + <TabLink to={`/profile/${userId}`}>Timeline</TabLink>
44 + <TabLink to={`/profile/${userId}/bio`}>Bio</TabLink>
45 + </TabBar>
46 + );
47 +}
48 +
49 function ProfilePage(props, data) {
50 return (
51 <>
52 <h2>{data.user.name}</h2>
30 - <Suspense fallback={<h3>Loading Timeline...</h3>}>
31 - <data.ProfileTimeline />
53 + <ProfileTabNav userId={data.user.id} />
54 + <Suspense fallback={<h3>Loading...</h3>}>
55 + <data.Tab />
56 </Suspense>
57 </>
58 );
fixtures/blocks/src/server/ProfileTimeline.block.js
+2 -7
@@ -15,7 +15,7 @@ import {fetch} from 'react-data/fetch';
15 import PostList from './PostList';
16
17 function load(userId) {
18 - const postsByUser = fetch(`/posts?userId=${userId}`).json();
18 + const postsByUser = fetch(`/posts?userId=${userId}&_expand=user`).json();
19 return {
20 posts: <PostList posts={postsByUser} />,
21 };
@@ -24,12 +24,7 @@ function load(userId) {
24 // Client
25
26 function ProfileTimeline(props, data) {
27 - return (
28 - <>
29 - <h3>Timeline</h3>
30 - {data.posts}
31 - </>
32 - );
27 + return <>{data.posts}</>;
28 }
29
30 export default block(ProfileTimeline, load);