diff --git a/fixtures/blocks/db.json b/fixtures/blocks/db.json
index 8b2067d51a..1d7ec30679 100644
--- a/fixtures/blocks/db.json
+++ b/fixtures/blocks/db.json
@@ -23,37 +23,55 @@
{
"id": 1,
"body": "Hey there",
- "postId": 1
+ "postId": 1,
+ "userId": 1
},
{
"id": 2,
"body": "Welcome to the chat",
- "postId": 1
+ "postId": 1,
+ "userId": 2
},
{
"id": 3,
"body": "What editor/font are you using?",
- "postId": 2
+ "postId": 2,
+ "userId": 2
},
{
"id": 4,
"body": "It's always been hard",
- "postId": 3
+ "postId": 3,
+ "userId": 1
},
{
"id": 5,
"body": "It's still easy",
- "postId": 3
+ "postId": 3,
+ "userId": 2
}
],
"users": [{
"id": 1,
- "name": "Sebastian"
+ "name": "Sebastian",
+ "bioId": 10
}, {
"id": 2,
- "name": "Sophie"
+ "name": "Sophie",
+ "bioId": 20
}, {
"id": 3,
- "name": "Dan"
+ "name": "Dan",
+ "bioId": 30
+ }],
+ "bios": [{
+ "id": 10,
+ "text": "I like European movies"
+ }, {
+ "id": 20,
+ "text": "I like math puzzles"
+ }, {
+ "id": 30,
+ "text": "I like reading twitter"
}]
}
diff --git a/fixtures/blocks/src/Router.js b/fixtures/blocks/src/Router.js
index 6d3295004c..19fb76bd8e 100644
--- a/fixtures/blocks/src/Router.js
+++ b/fixtures/blocks/src/Router.js
@@ -24,17 +24,24 @@ const initialState = {
// TODO: use this for invalidation.
cache: createCache(),
url: initialUrl,
+ pendingUrl: initialUrl,
RootBlock: loadApp(initialUrl),
};
function reducer(state, action) {
switch (action.type) {
- case 'navigate':
+ case 'startNavigation':
+ return {
+ ...state,
+ pendingUrl: action.url,
+ };
+ case 'completeNavigation':
// TODO: cancel previous fetch?
return {
- cache: state.cache,
+ ...state,
url: action.url,
- RootBlock: loadApp(action.url),
+ pendingUrl: action.url,
+ RootBlock: action.RootBlock,
};
default:
throw new Error();
@@ -44,7 +51,7 @@ function reducer(state, action) {
function Router() {
const [state, dispatch] = useReducer(reducer, initialState);
const [startTransition, isPending] = useTransition({
- timeoutMs: 3000,
+ timeoutMs: 1500,
});
useEffect(() => {
@@ -56,12 +63,16 @@ function Router() {
startTransition(() => {
// TODO: Here, There, and Everywhere.
// TODO: Instant Transitions, somehow.
- // TODO: Buttons should update immediately.
dispatch({
- type: 'navigate',
+ type: 'completeNavigation',
+ RootBlock: loadApp(url),
url,
});
});
+ dispatch({
+ type: 'startNavigation',
+ url,
+ });
},
[startTransition]
);
@@ -76,10 +87,11 @@ function Router() {
const routeContext = useMemo(
() => ({
+ pendingUrl: state.pendingUrl,
url: state.url,
navigate,
}),
- [state.url, navigate]
+ [state.url, state.pendingUrl, navigate]
);
return (
diff --git a/fixtures/blocks/src/client/Link.js b/fixtures/blocks/src/client/Link.js
new file mode 100644
index 0000000000..1f1f535da1
--- /dev/null
+++ b/fixtures/blocks/src/client/Link.js
@@ -0,0 +1,25 @@
+/**
+ * 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 {useRouter} from './RouterContext';
+
+export default function Link({to, children, ...rest}) {
+ const {navigate} = useRouter();
+ return (
+ {
+ e.preventDefault();
+ window.history.pushState(null, null, to);
+ navigate(to);
+ }}
+ {...rest}>
+ {children}
+
+ );
+}
diff --git a/fixtures/blocks/src/client/Shell.js b/fixtures/blocks/src/client/Shell.js
index e6e1a005f9..07b74ba88a 100644
--- a/fixtures/blocks/src/client/Shell.js
+++ b/fixtures/blocks/src/client/Shell.js
@@ -6,57 +6,25 @@
*/
import * as React from 'react';
-import {useRouter} from './RouterContext';
+import {TabBar, TabLink} from './TabNav';
-function TabBar({children}) {
- return (
-
- {children}
-
- );
-}
+// TODO: Error Boundaries.
-function TabLink({to, children}) {
- const {url: activeUrl, navigate} = useRouter();
- const active = activeUrl === to;
- if (active) {
- return (
-
- {children}
-
- );
- }
+function MainTabNav() {
return (
- {
- e.preventDefault();
- window.history.pushState(null, null, to);
- navigate(to);
- }}>
- {children}
-
+
+ Home
+
+ Profile
+
+
);
}
export default function Shell({children}) {
return (
<>
-
- Home
- Profile
-
-
+
{children}
>
);
diff --git a/fixtures/blocks/src/client/TabNav.js b/fixtures/blocks/src/client/TabNav.js
new file mode 100644
index 0000000000..142767263f
--- /dev/null
+++ b/fixtures/blocks/src/client/TabNav.js
@@ -0,0 +1,50 @@
+/**
+ * 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 Link from './Link';
+import {useRouter} from './RouterContext';
+
+export function TabBar({children}) {
+ return (
+
+ {children}
+
+ );
+}
+
+export function TabLink({to, partial, children}) {
+ const {pendingUrl: activeUrl} = useRouter();
+ const active = partial ? activeUrl.startsWith(to) : activeUrl === to;
+ if (active) {
+ return (
+
+ {children}
+
+ );
+ }
+ return (
+
+ {children}
+
+ );
+}
diff --git a/fixtures/blocks/src/server/App.block.js b/fixtures/blocks/src/server/App.block.js
index 9347bbe0cf..a02cedd3ce 100644
--- a/fixtures/blocks/src/server/App.block.js
+++ b/fixtures/blocks/src/server/App.block.js
@@ -16,15 +16,13 @@ import loadProfilePage from './ProfilePage.block';
function load(url) {
let Page;
- switch (url) {
- case '/':
- Page = loadFeedPage();
- break;
- case '/profile':
- Page = loadProfilePage(3);
- break;
- default:
- throw Error('Not found');
+ 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};
}
@@ -33,6 +31,8 @@ function load(url) {
import Shell from '../client/Shell';
+// TODO: some notion of key.
+
function App(props, data) {
return (
diff --git a/fixtures/blocks/src/server/Comments.block.js b/fixtures/blocks/src/server/Comments.block.js
index 3e021519fd..fdea53adee 100644
--- a/fixtures/blocks/src/server/Comments.block.js
+++ b/fixtures/blocks/src/server/Comments.block.js
@@ -15,19 +15,25 @@ import {fetch} from 'react-data/fetch';
function load(postId) {
return {
- comments: fetch(`/comments?postId=${postId}`).json(),
+ comments: fetch(`/comments?postId=${postId}&_expand=user`).json(),
};
}
// Client
+import Link from '../client/Link';
+
function Comments(props, data) {
return (
<>
Comments
- {data.comments.slice(0, 5).map(item => (
- - {item.body}
+ {data.comments.slice(0, 5).map(comment => (
+ -
+ {comment.body}
+ {' • '}
+ {comment.user.name}
+
))}
>
diff --git a/fixtures/blocks/src/server/FeedPage.block.js b/fixtures/blocks/src/server/FeedPage.block.js
index 56ebcaffe2..9fdbb414fe 100644
--- a/fixtures/blocks/src/server/FeedPage.block.js
+++ b/fixtures/blocks/src/server/FeedPage.block.js
@@ -15,7 +15,7 @@ import {fetch} from 'react-data/fetch';
import PostList from './PostList';
function load(params) {
- const allPosts = fetch('/posts').json();
+ const allPosts = fetch('/posts?_expand=user').json();
return {
posts: ,
};
diff --git a/fixtures/blocks/src/server/Post.block.js b/fixtures/blocks/src/server/Post.block.js
index 23ce58cbd3..ffc3e63b38 100644
--- a/fixtures/blocks/src/server/Post.block.js
+++ b/fixtures/blocks/src/server/Post.block.js
@@ -21,6 +21,8 @@ function load(postId) {
// Client
+import Link from '../client/Link';
+
function Post(props, data) {
return (
-
{props.post.title}
+
+ {props.post.title}
+ {' by '}
+
+ {props.post.user.name}
+
+
{props.post.body}
Loading comments...}
diff --git a/fixtures/blocks/src/server/PostList.js b/fixtures/blocks/src/server/PostList.js
index 2baf1da4fc..f479da5eb2 100644
--- a/fixtures/blocks/src/server/PostList.js
+++ b/fixtures/blocks/src/server/PostList.js
@@ -17,7 +17,7 @@ export default function PostList({posts}) {
return (
{posts.map(post => {
- preload(`/comments?postId=${post.id}`);
+ preload(`/comments?postId=${post.id}&_expand=user`);
const Post = loadPost(post.id);
return (
}>
diff --git a/fixtures/blocks/src/server/ProfileBio.block.js b/fixtures/blocks/src/server/ProfileBio.block.js
new file mode 100644
index 0000000000..1142524de2
--- /dev/null
+++ b/fixtures/blocks/src/server/ProfileBio.block.js
@@ -0,0 +1,34 @@
+/**
+ * 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 {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) {
+ return (
+ <>
+ {data.user.name}'s Bio
+ {data.bio.text}
+ >
+ );
+}
+
+export default block(ProfileBio, load);
diff --git a/fixtures/blocks/src/server/ProfilePage.block.js b/fixtures/blocks/src/server/ProfilePage.block.js
index 6ee61e56b4..668c44b3d7 100644
--- a/fixtures/blocks/src/server/ProfilePage.block.js
+++ b/fixtures/blocks/src/server/ProfilePage.block.js
@@ -12,23 +12,47 @@ import {block, Suspense} from 'react';
// Server
import {fetch} from 'react-data/fetch';
+import loadProfileBio from './ProfileBio.block';
import loadProfileTimeline from './ProfileTimeline.block';
-function load(userId) {
+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 {
- user: fetch(`/users/${userId}`).json(),
- ProfileTimeline: loadProfileTimeline(userId),
+ Tab,
+ user,
};
}
// Client
+import {TabBar, TabLink} from '../client/TabNav';
+
+function ProfileTabNav({userId}) {
+ // TODO: Don't hardcode ID.
+ return (
+
+ Timeline
+ Bio
+
+ );
+}
+
function ProfilePage(props, data) {
return (
<>
{data.user.name}
- Loading Timeline...}>
-
+
+ Loading...}>
+
>
);
diff --git a/fixtures/blocks/src/server/ProfileTimeline.block.js b/fixtures/blocks/src/server/ProfileTimeline.block.js
index c1c0978a16..8d42eda7a5 100644
--- a/fixtures/blocks/src/server/ProfileTimeline.block.js
+++ b/fixtures/blocks/src/server/ProfileTimeline.block.js
@@ -15,7 +15,7 @@ import {fetch} from 'react-data/fetch';
import PostList from './PostList';
function load(userId) {
- const postsByUser = fetch(`/posts?userId=${userId}`).json();
+ const postsByUser = fetch(`/posts?userId=${userId}&_expand=user`).json();
return {
posts: ,
};
@@ -24,12 +24,7 @@ function load(userId) {
// Client
function ProfileTimeline(props, data) {
- return (
- <>
- Timeline
- {data.posts}
- >
- );
+ return <>{data.posts}>;
}
export default block(ProfileTimeline, load);