Skip to content

Commit f6fcae5

Browse files
authored
[Blocks] Add Shell to Fixture (facebook#18803)
* [Blocks] Add Feed page to fixture * Add minimal routing * Always show post with comments
1 parent d830cd9 commit f6fcae5

18 files changed

+500
-125
lines changed

fixtures/blocks/db.json

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@
22
"posts": [
33
{
44
"id": 1,
5-
"title": "My First Post",
5+
"userId": 2,
6+
"title": "Welcome",
67
"body": "Hello, world!"
78
},
89
{
910
"id": 2,
10-
"title": "My Second Post",
11+
"userId": 3,
12+
"title": "A Guide to useEffect",
1113
"body": "Let me tell you everything about useEffect"
1214
},
1315
{
1416
"id": 3,
15-
"title": "My Third Post",
16-
"body": "Why is everything so complicated?"
17+
"userId": 1,
18+
"title": "Here and There",
19+
"body": "Browsers are smart"
1720
}
1821
],
1922
"comments": [
@@ -42,5 +45,15 @@
4245
"body": "It's still easy",
4346
"postId": 3
4447
}
45-
]
48+
],
49+
"users": [{
50+
"id": 1,
51+
"name": "Sebastian"
52+
}, {
53+
"id": 2,
54+
"name": "Sophie"
55+
}, {
56+
"id": 3,
57+
"name": "Dan"
58+
}]
4659
}

fixtures/blocks/delay.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
module.exports = (req, res, next) => {
9+
if (req.query.delay) {
10+
setTimeout(next, Number(req.query.delay));
11+
} else {
12+
next();
13+
}
14+
};

fixtures/blocks/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "blocks",
33
"version": "0.1.0",
44
"private": true,
5+
"proxy": "http://localhost:3001/",
56
"dependencies": {
67
"concurrently": "^5.2.0",
78
"json-server": "^0.16.1",
@@ -13,7 +14,7 @@
1314
"prestart": "cp -r ../../build/node_modules/* ./node_modules/",
1415
"prebuild": "cp -r ../../build/node_modules/* ./node_modules/",
1516
"start": "concurrently \"npm run start:client\" \"npm run start:api\"",
16-
"start:api": "json-server --watch db.json --port 3001 --delay 300",
17+
"start:api": "json-server --watch db.json --port 3001 --delay 1000 --middlewares delay.js",
1718
"start:client": "react-scripts start",
1819
"build": "react-scripts build",
1920
"eject": "react-scripts eject"

fixtures/blocks/src/App.js

Lines changed: 0 additions & 73 deletions
This file was deleted.

fixtures/blocks/src/Post.js

Lines changed: 0 additions & 41 deletions
This file was deleted.

fixtures/blocks/src/Router.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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 React, {
9+
useReducer,
10+
useEffect,
11+
useTransition,
12+
useCallback,
13+
useMemo,
14+
Suspense,
15+
} from 'react';
16+
import {createCache, CacheProvider} from 'react/unstable-cache';
17+
import {RouterProvider} from './client/RouterContext';
18+
// TODO: entry point. This can't really done in the client code.
19+
import loadApp from './server/App.block';
20+
21+
const initialUrl = window.location.pathname;
22+
23+
const initialState = {
24+
// TODO: use this for invalidation.
25+
cache: createCache(),
26+
url: initialUrl,
27+
RootBlock: loadApp(initialUrl),
28+
};
29+
30+
function reducer(state, action) {
31+
switch (action.type) {
32+
case 'navigate':
33+
// TODO: cancel previous fetch?
34+
return {
35+
cache: state.cache,
36+
url: action.url,
37+
RootBlock: loadApp(action.url),
38+
};
39+
default:
40+
throw new Error();
41+
}
42+
}
43+
44+
function Router() {
45+
const [state, dispatch] = useReducer(reducer, initialState);
46+
const [startTransition, isPending] = useTransition({
47+
timeoutMs: 3000,
48+
});
49+
50+
useEffect(() => {
51+
document.body.style.cursor = isPending ? 'wait' : '';
52+
}, [isPending]);
53+
54+
const navigate = useCallback(
55+
url => {
56+
startTransition(() => {
57+
// TODO: Here, There, and Everywhere.
58+
// TODO: Instant Transitions, somehow.
59+
// TODO: Buttons should update immediately.
60+
dispatch({
61+
type: 'navigate',
62+
url,
63+
});
64+
});
65+
},
66+
[startTransition]
67+
);
68+
69+
useEffect(() => {
70+
const listener = () => {
71+
navigate(window.location.pathname);
72+
};
73+
window.addEventListener('popstate', listener);
74+
return () => window.removeEventListener('popstate', listener);
75+
}, [navigate]);
76+
77+
const routeContext = useMemo(
78+
() => ({
79+
url: state.url,
80+
navigate,
81+
}),
82+
[state.url, navigate]
83+
);
84+
85+
return (
86+
<Suspense fallback={<h2>Loading...</h2>}>
87+
<CacheProvider value={state.cache}>
88+
<RouterProvider value={routeContext}>
89+
<state.RootBlock />
90+
</RouterProvider>
91+
</CacheProvider>
92+
</Suspense>
93+
);
94+
}
95+
96+
export default Router;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import {createContext, useContext} from 'react';
2+
3+
const RouterContext = createContext(null);
4+
5+
export const RouterProvider = RouterContext.Provider;
6+
7+
export function useRouter() {
8+
return useContext(RouterContext);
9+
}

fixtures/blocks/src/client/Shell.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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+
function TabBar({children}) {
12+
return (
13+
<div style={{border: '1px solid #aaa', padding: 20, width: 500}}>
14+
{children}
15+
</div>
16+
);
17+
}
18+
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+
}
34+
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>
49+
);
50+
}
51+
52+
export default function Shell({children}) {
53+
return (
54+
<>
55+
<TabBar>
56+
<TabLink to="/">Home</TabLink>
57+
<TabLink to="/profile">Profile</TabLink>
58+
</TabBar>
59+
<br />
60+
{children}
61+
</>
62+
);
63+
}

fixtures/blocks/src/index.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@ body {
22
font-family: Helvetica;
33
padding-left: 10px;
44
}
5+
6+
* {
7+
box-sizing: border-box;
8+
}

fixtures/blocks/src/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
import React from 'react';
99
import ReactDOM from 'react-dom';
1010
import './index.css';
11-
import App from './App';
11+
import Router from './Router';
1212

13-
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
13+
ReactDOM.createRoot(document.getElementById('root')).render(<Router />);

0 commit comments

Comments
 (0)