Skip to content

Commit b2de7cf

Browse files
committed
Nesting Fixture
1 parent 8d57ca5 commit b2de7cf

File tree

18 files changed

+394
-0
lines changed

18 files changed

+394
-0
lines changed

fixtures/nesting/.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
EXTEND_ESLINT=true
2+
SKIP_PREFLIGHT_CHECK=true

fixtures/nesting/.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
src/*/node_modules

fixtures/nesting/.gitignore

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# production
12+
/build
13+
14+
# misc
15+
.DS_Store
16+
.env.local
17+
.env.development.local
18+
.env.test.local
19+
.env.production.local
20+
21+
npm-debug.log*
22+
yarn-debug.log*
23+
yarn-error.log*
24+
25+
# copies of shared
26+
src/*/shared
27+
src/*/node_modules

fixtures/nesting/package.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "react-nesting-example",
3+
"version": "0.1.0",
4+
"private": true,
5+
"dependencies": {
6+
"react-scripts": "3.4.1"
7+
},
8+
"scripts": {
9+
"postinstall": "run-p install:*",
10+
"install:legacy": "cd src/legacy && npm install",
11+
"install:modern": "cd src/modern && npm install",
12+
"copy:legacy": "cpx 'src/shared/**' 'src/legacy/shared/'",
13+
"copy:modern": "cpx 'src/shared/**' 'src/modern/shared/'",
14+
"watch:legacy": "cpx 'src/shared/**' 'src/legacy/shared/' --watch --no-initial",
15+
"watch:modern": "cpx 'src/shared/**' 'src/modern/shared/' --watch --no-initial",
16+
"prebuild": "run-p copy:*",
17+
"prestart": "run-p copy:*",
18+
"start": "run-p start-app watch:*",
19+
"start-app": "react-scripts start",
20+
"build": "react-scripts build",
21+
"eject": "react-scripts eject"
22+
},
23+
"eslintConfig": {
24+
"extends": "react-app"
25+
},
26+
"browserslist": {
27+
"production": [
28+
">0.2%",
29+
"not dead",
30+
"not op_mini all"
31+
],
32+
"development": [
33+
"last 1 chrome version",
34+
"last 1 firefox version",
35+
"last 1 safari version"
36+
]
37+
},
38+
"devDependencies": {
39+
"cpx": "^1.5.0",
40+
"npm-run-all": "^4.1.5"
41+
}
42+
}

fixtures/nesting/public/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>React App</title>
7+
</head>
8+
<body>
9+
<noscript>You need to enable JavaScript to run this app.</noscript>
10+
<div id="root"></div>
11+
</body>
12+
</html>

fixtures/nesting/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './modern/index';
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react';
2+
import {Component} from 'react';
3+
import {findDOMNode} from 'react-dom';
4+
import {Link} from 'react-router-dom';
5+
import ThemeContext from './shared/ThemeContext';
6+
import Clock from './shared/Clock';
7+
8+
export default class AboutSection extends Component {
9+
static contextType = ThemeContext;
10+
11+
componentDidMount() {
12+
// The modern app is wrapped in StrictMode,
13+
// but the legacy bits can still use old APIs.
14+
findDOMNode(this);
15+
}
16+
17+
render() {
18+
const theme = this.context;
19+
return (
20+
<div style={{border: '1px dashed black', padding: 20}}>
21+
<h3>src/legacy/Greeting.js</h3>
22+
<h4 style={{color: theme}}>
23+
This component is rendered by the nested React.
24+
</h4>
25+
<Clock />
26+
<b>
27+
<Link to="/">Go to Home</Link>
28+
</b>
29+
</div>
30+
);
31+
}
32+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/* eslint-disable react/jsx-pascal-case */
2+
3+
import React from 'react';
4+
import ReactDOM from 'react-dom';
5+
import ThemeContext from './shared/ThemeContext';
6+
7+
// Note: this is a semi-private API, but it's ok to use it
8+
// if we never inspect the values, and only pass them through.
9+
import {__RouterContext} from 'react-router';
10+
11+
// Pass through every context required by this tree.
12+
// The context object is populated in src/modern/withLegacyRoot.
13+
function Bridge({children, context}) {
14+
return (
15+
<ThemeContext.Provider value={context.theme}>
16+
<__RouterContext.Provider value={context.router}>
17+
{children}
18+
</__RouterContext.Provider>
19+
</ThemeContext.Provider>
20+
);
21+
}
22+
23+
export default function createLegacyRoot(container) {
24+
return {
25+
render(Component, props, context) {
26+
ReactDOM.render(
27+
<Bridge context={context}>
28+
<Component {...props} />
29+
</Bridge>,
30+
container
31+
);
32+
},
33+
unmount() {
34+
ReactDOM.unmountComponentAtNode(container);
35+
},
36+
};
37+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"private": true,
3+
"name": "react-nesting-example-legacy",
4+
"dependencies": {
5+
"react": "0.0.0-3d0895557",
6+
"react-dom": "0.0.0-3d0895557",
7+
"react-router-dom": "5.2.0"
8+
}
9+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from 'react';
2+
import {useContext} from 'react';
3+
import {findDOMNode} from 'react-dom';
4+
5+
import ThemeContext from './shared/ThemeContext';
6+
import withLegacyRoot from './withLegacyRoot';
7+
8+
// Lazy-load a component from the bundle using legacy React.
9+
const Greeting = withLegacyRoot(() => import('../legacy/Greeting'));
10+
11+
export default function AboutPage() {
12+
findDOMNode();
13+
const theme = useContext(ThemeContext);
14+
return (
15+
<>
16+
<h2>src/modern/AboutPage.js</h2>
17+
<h3 style={{color: theme}}>
18+
This component is rendered by the outer React.
19+
</h3>
20+
<Greeting />
21+
</>
22+
);
23+
}

fixtures/nesting/src/modern/App.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React from 'react';
2+
import {useState, Suspense} from 'react';
3+
import {BrowserRouter, Switch, Route} from 'react-router-dom';
4+
5+
import HomePage from './HomePage';
6+
import AboutPage from './AboutPage';
7+
import ThemeContext from './shared/ThemeContext';
8+
9+
export default function App() {
10+
const [theme, setTheme] = useState('slategrey');
11+
12+
function handleToggleClick() {
13+
if (theme === 'slategrey') {
14+
setTheme('hotpink');
15+
} else {
16+
setTheme('slategrey');
17+
}
18+
}
19+
20+
return (
21+
<BrowserRouter>
22+
<ThemeContext.Provider value={theme}>
23+
<div style={{fontFamily: 'sans-serif'}}>
24+
<div
25+
style={{
26+
margin: 20,
27+
padding: 20,
28+
border: '1px solid black',
29+
minHeight: 300,
30+
}}>
31+
<button onClick={handleToggleClick}>Toggle Theme Context</button>
32+
<br />
33+
<Suspense fallback={<Spinner />}>
34+
<Switch>
35+
<Route path="/about">
36+
<AboutPage />
37+
</Route>
38+
<Route path="/">
39+
<HomePage />
40+
</Route>
41+
</Switch>
42+
</Suspense>
43+
</div>
44+
</div>
45+
</ThemeContext.Provider>
46+
</BrowserRouter>
47+
);
48+
}
49+
50+
function Spinner() {
51+
return null;
52+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
import {useContext} from 'react';
3+
import {Link} from 'react-router-dom';
4+
5+
import ThemeContext from './shared/ThemeContext';
6+
import Clock from './shared/Clock';
7+
8+
export default function HomePage() {
9+
const theme = useContext(ThemeContext);
10+
return (
11+
<>
12+
<h2>src/modern/HomePage.js</h2>
13+
<h3 style={{color: theme}}>
14+
This component is rendered by the outer React.
15+
</h3>
16+
<Clock />
17+
<b>
18+
<Link to="/about">Go to About</Link>
19+
</b>
20+
</>
21+
);
22+
}

fixtures/nesting/src/modern/index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
import {StrictMode} from 'react';
3+
import ReactDOM from 'react-dom';
4+
import App from './App';
5+
6+
ReactDOM.render(
7+
<StrictMode>
8+
<App />
9+
</StrictMode>,
10+
document.getElementById('root')
11+
);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"private": true,
3+
"name": "react-nesting-example-modern",
4+
"dependencies": {
5+
"react": "0.0.0-3d0895557",
6+
"react-dom": "0.0.0-3d0895557",
7+
"react-router-dom": "5.2.0"
8+
}
9+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import React from 'react';
2+
import {useContext, useMemo, useRef, useState, useLayoutEffect} from 'react';
3+
4+
import {__RouterContext} from 'react-router';
5+
import ThemeContext from './shared/ThemeContext';
6+
7+
let createLegacyRootRecord = {
8+
status: 'pending',
9+
promise: null,
10+
result: null,
11+
};
12+
13+
export default function withLegacy(getLegacyComponent) {
14+
let componentRecord = {
15+
status: 'pending',
16+
promise: null,
17+
result: null,
18+
};
19+
20+
return function Wrapper(props) {
21+
const createLegacyRoot = readRecord(createLegacyRootRecord, () =>
22+
import('../legacy/createLegacyRoot')
23+
).default;
24+
const Component = readRecord(componentRecord, getLegacyComponent).default;
25+
const containerRef = useRef(null);
26+
const [root, setRoot] = useState(null);
27+
28+
// Popluate every contexts we want the legacy subtree to see.
29+
// Then in src/legacy/createLegacyRoot we will apply them.
30+
const theme = useContext(ThemeContext);
31+
const router = useContext(__RouterContext);
32+
const context = useMemo(
33+
() => ({
34+
theme,
35+
router,
36+
}),
37+
[theme, router]
38+
);
39+
40+
// Create/unmount.
41+
useLayoutEffect(() => {
42+
if (!root) {
43+
let r = createLegacyRoot(containerRef.current);
44+
setRoot(r);
45+
return () => r.unmount();
46+
}
47+
}, [createLegacyRoot, root]);
48+
49+
// Mout/update.
50+
useLayoutEffect(() => {
51+
if (root) {
52+
root.render(Component, props, context);
53+
}
54+
}, [Component, root, props, context]);
55+
56+
return <div style={{display: 'contents'}} ref={containerRef} />;
57+
};
58+
}
59+
60+
// This is similar to React.lazy, but implemented manually.
61+
// We use this to Suspend rendering of this component until
62+
// we fetch the component and the legacy React to render it.
63+
function readRecord(record, createPromise) {
64+
if (record.status === 'fulfilled') {
65+
return record.result;
66+
}
67+
if (record.status === 'rejected') {
68+
throw record.result;
69+
}
70+
if (!record.promise) {
71+
record.promise = createPromise().then(
72+
value => {
73+
if (record.status === 'pending') {
74+
record.status = 'fulfilled';
75+
record.promise = null;
76+
record.result = value;
77+
}
78+
},
79+
error => {
80+
if (record.status === 'pending') {
81+
record.status = 'rejected';
82+
record.promise = null;
83+
record.result = error;
84+
}
85+
}
86+
);
87+
}
88+
throw record.promise;
89+
}

fixtures/nesting/src/shared/Clock.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import React from 'react';
2+
3+
import useTime from './useTime';
4+
5+
export default function Clock() {
6+
const time = useTime();
7+
return <p>Time: {time}</p>;
8+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import {createContext} from 'react';
2+
3+
const ThemeContext = createContext(null);
4+
5+
export default ThemeContext;

0 commit comments

Comments
 (0)