Skip to content

Commit

Permalink
Nesting Fixture
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon committed Aug 5, 2020
1 parent 8d57ca5 commit df63d68
Show file tree
Hide file tree
Showing 18 changed files with 394 additions and 0 deletions.
2 changes: 2 additions & 0 deletions fixtures/nesting/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
EXTEND_ESLINT=true
SKIP_PREFLIGHT_CHECK=true
1 change: 1 addition & 0 deletions fixtures/nesting/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/*/node_modules
27 changes: 27 additions & 0 deletions fixtures/nesting/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*

# copies of shared
src/*/shared
src/*/node_modules
42 changes: 42 additions & 0 deletions fixtures/nesting/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "react-nesting-example",
"version": "0.1.0",
"private": true,
"dependencies": {
"react-scripts": "3.4.1"
},
"scripts": {
"postinstall": "run-p install:*",
"install:legacy": "cd src/legacy && npm install",
"install:modern": "cd src/modern && npm install",
"copy:legacy": "cpx 'src/shared/**' 'src/legacy/shared/'",
"copy:modern": "cpx 'src/shared/**' 'src/modern/shared/'",
"watch:legacy": "cpx 'src/shared/**' 'src/legacy/shared/' --watch --no-initial",
"watch:modern": "cpx 'src/shared/**' 'src/modern/shared/' --watch --no-initial",
"prebuild": "run-p copy:*",
"prestart": "run-p copy:*",
"start": "run-p start-app watch:*",
"start-app": "react-scripts start",
"build": "react-scripts build",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"cpx": "^1.5.0",
"npm-run-all": "^4.1.5"
}
}
12 changes: 12 additions & 0 deletions fixtures/nesting/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
1 change: 1 addition & 0 deletions fixtures/nesting/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './modern/index';
32 changes: 32 additions & 0 deletions fixtures/nesting/src/legacy/Greeting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import {Component} from 'react';
import {findDOMNode} from 'react-dom';
import {Link} from 'react-router-dom';
import ThemeContext from './shared/ThemeContext';
import Clock from './shared/Clock';

export default class AboutSection extends Component {
static contextType = ThemeContext;

componentDidMount() {
// The modern app is wrapped in StrictMode,
// but the legacy bits can still use old APIs.
findDOMNode(this);
}

render() {
const theme = this.context;
return (
<div style={{border: '1px dashed black', padding: 20}}>
<h3>src/legacy/Greeting.js</h3>
<h4 style={{color: theme}}>
This component is rendered by the nested React.
</h4>
<Clock />
<b>
<Link to="/">Go to Home</Link>
</b>
</div>
);
}
}
37 changes: 37 additions & 0 deletions fixtures/nesting/src/legacy/createLegacyRoot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* eslint-disable react/jsx-pascal-case */

import React from 'react';
import ReactDOM from 'react-dom';
import ThemeContext from './shared/ThemeContext';

// Note: this is a semi-private API, but it's ok to use it
// if we never inspect the values, and only pass them through.
import {__RouterContext} from 'react-router';

// Pass through every context required by this tree.
// The context object is populated in src/modern/withLegacyRoot.
function Bridge({children, context}) {
return (
<ThemeContext.Provider value={context.theme}>
<__RouterContext.Provider value={context.router}>
{children}
</__RouterContext.Provider>
</ThemeContext.Provider>
);
}

export default function createLegacyRoot(container) {
return {
render(Component, props, context) {
ReactDOM.render(
<Bridge context={context}>
<Component {...props} />
</Bridge>,
container
);
},
unmount() {
ReactDOM.unmountComponentAtNode(container);
},
};
}
9 changes: 9 additions & 0 deletions fixtures/nesting/src/legacy/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"private": true,
"name": "react-nesting-example-legacy",
"dependencies": {
"react": "0.0.0-3d0895557",
"react-dom": "0.0.0-3d0895557",
"react-router-dom": "5.2.0"
}
}
23 changes: 23 additions & 0 deletions fixtures/nesting/src/modern/AboutPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import {useContext} from 'react';
import {findDOMNode} from 'react-dom';

import ThemeContext from './shared/ThemeContext';
import lazyLegacyRoot from './lazyLegacyRoot';

// Lazy-load a component from the bundle using legacy React.
const Greeting = lazyLegacyRoot(() => import('../legacy/Greeting'));

export default function AboutPage() {
findDOMNode();
const theme = useContext(ThemeContext);
return (
<>
<h2>src/modern/AboutPage.js</h2>
<h3 style={{color: theme}}>
This component is rendered by the outer React.
</h3>
<Greeting />
</>
);
}
52 changes: 52 additions & 0 deletions fixtures/nesting/src/modern/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import {useState, Suspense} from 'react';
import {BrowserRouter, Switch, Route} from 'react-router-dom';

import HomePage from './HomePage';
import AboutPage from './AboutPage';
import ThemeContext from './shared/ThemeContext';

export default function App() {
const [theme, setTheme] = useState('slategrey');

function handleToggleClick() {
if (theme === 'slategrey') {
setTheme('hotpink');
} else {
setTheme('slategrey');
}
}

return (
<BrowserRouter>
<ThemeContext.Provider value={theme}>
<div style={{fontFamily: 'sans-serif'}}>
<div
style={{
margin: 20,
padding: 20,
border: '1px solid black',
minHeight: 300,
}}>
<button onClick={handleToggleClick}>Toggle Theme Context</button>
<br />
<Suspense fallback={<Spinner />}>
<Switch>
<Route path="/about">
<AboutPage />
</Route>
<Route path="/">
<HomePage />
</Route>
</Switch>
</Suspense>
</div>
</div>
</ThemeContext.Provider>
</BrowserRouter>
);
}

function Spinner() {
return null;
}
22 changes: 22 additions & 0 deletions fixtures/nesting/src/modern/HomePage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import {useContext} from 'react';
import {Link} from 'react-router-dom';

import ThemeContext from './shared/ThemeContext';
import Clock from './shared/Clock';

export default function HomePage() {
const theme = useContext(ThemeContext);
return (
<>
<h2>src/modern/HomePage.js</h2>
<h3 style={{color: theme}}>
This component is rendered by the outer React.
</h3>
<Clock />
<b>
<Link to="/about">Go to About</Link>
</b>
</>
);
}
11 changes: 11 additions & 0 deletions fixtures/nesting/src/modern/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import {StrictMode} from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
<StrictMode>
<App />
</StrictMode>,
document.getElementById('root')
);
89 changes: 89 additions & 0 deletions fixtures/nesting/src/modern/lazyLegacyRoot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react';
import {useContext, useMemo, useRef, useState, useLayoutEffect} from 'react';

import {__RouterContext} from 'react-router';
import ThemeContext from './shared/ThemeContext';

let rendererModule = {
status: 'pending',
promise: null,
result: null,
};

export default function lazyLegacyRoot(getLegacyComponent) {
let componentModule = {
status: 'pending',
promise: null,
result: null,
};

return function Wrapper(props) {
const createLegacyRoot = readRecord(rendererModule, () =>
import('../legacy/createLegacyRoot')
).default;
const Component = readRecord(componentModule, getLegacyComponent).default;
const containerRef = useRef(null);
const [root, setRoot] = useState(null);

// Popluate every contexts we want the legacy subtree to see.
// Then in src/legacy/createLegacyRoot we will apply them.
const theme = useContext(ThemeContext);
const router = useContext(__RouterContext);
const context = useMemo(
() => ({
theme,
router,
}),
[theme, router]
);

// Create/unmount.
useLayoutEffect(() => {
if (!root) {
let r = createLegacyRoot(containerRef.current);
setRoot(r);
return () => r.unmount();
}
}, [createLegacyRoot, root]);

// Mout/update.
useLayoutEffect(() => {
if (root) {
root.render(Component, props, context);
}
}, [Component, root, props, context]);

return <div style={{display: 'contents'}} ref={containerRef} />;
};
}

// This is similar to React.lazy, but implemented manually.
// We use this to Suspend rendering of this component until
// we fetch the component and the legacy React to render it.
function readRecord(record, createPromise) {
if (record.status === 'fulfilled') {
return record.result;
}
if (record.status === 'rejected') {
throw record.result;
}
if (!record.promise) {
record.promise = createPromise().then(
value => {
if (record.status === 'pending') {
record.status = 'fulfilled';
record.promise = null;
record.result = value;
}
},
error => {
if (record.status === 'pending') {
record.status = 'rejected';
record.promise = null;
record.result = error;
}
}
);
}
throw record.promise;
}
9 changes: 9 additions & 0 deletions fixtures/nesting/src/modern/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"private": true,
"name": "react-nesting-example-modern",
"dependencies": {
"react": "0.0.0-3d0895557",
"react-dom": "0.0.0-3d0895557",
"react-router-dom": "5.2.0"
}
}
8 changes: 8 additions & 0 deletions fixtures/nesting/src/shared/Clock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';

import useTime from './useTime';

export default function Clock() {
const time = useTime();
return <p>Time: {time}</p>;
}
5 changes: 5 additions & 0 deletions fixtures/nesting/src/shared/ThemeContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {createContext} from 'react';

const ThemeContext = createContext(null);

export default ThemeContext;
Loading

0 comments on commit df63d68

Please sign in to comment.