Skip to content

Commit eb3ea2d

Browse files
committed
Change SSR Fixture to use Partial Hydration
This requires the enableSuspenseServerRenderer flag to be manually enabled for the build to work.
1 parent e144a5e commit eb3ea2d

File tree

9 files changed

+153
-23
lines changed

9 files changed

+153
-23
lines changed

fixtures/ssr/src/components/App.js

+27-12
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
1-
import React, {Component} from 'react';
1+
import React, {useContext, useState, Suspense} from 'react';
22

33
import Chrome from './Chrome';
44
import Page from './Page';
5+
import Page2 from './Page2';
6+
import Theme from './Theme';
57

6-
export default class App extends Component {
7-
render() {
8-
return (
9-
<Chrome title="Hello World" assets={this.props.assets}>
10-
<div>
11-
<h1>Hello World</h1>
12-
<Page />
13-
</div>
14-
</Chrome>
15-
);
16-
}
8+
function LoadingIndicator() {
9+
let theme = useContext(Theme);
10+
return <div className={theme + '-loading'}>Loading...</div>;
11+
}
12+
13+
export default function App({assets}) {
14+
let [CurrentPage, switchPage] = useState(() => Page);
15+
return (
16+
<Chrome title="Hello World" assets={assets}>
17+
<div>
18+
<h1>Hello World</h1>
19+
<a className="link" onClick={() => switchPage(() => Page)}>
20+
Page 1
21+
</a>
22+
{' | '}
23+
<a className="link" onClick={() => switchPage(() => Page2)}>
24+
Page 2
25+
</a>
26+
<Suspense fallback={<LoadingIndicator />}>
27+
<CurrentPage />
28+
</Suspense>
29+
</div>
30+
</Chrome>
31+
);
1732
}

fixtures/ssr/src/components/Chrome.css

+24
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,27 @@ body {
33
padding: 0;
44
font-family: sans-serif;
55
}
6+
7+
body.light {
8+
background-color: #FFFFFF;
9+
color: #333333;
10+
}
11+
12+
body.dark {
13+
background-color: #000000;
14+
color: #CCCCCC;
15+
}
16+
17+
.light-loading {
18+
margin: 10px 0;
19+
padding: 10px;
20+
background-color: #CCCCCC;
21+
color: #666666;
22+
}
23+
24+
.dark-loading {
25+
margin: 10px 0;
26+
padding: 10px;
27+
background-color: #333333;
28+
color: #999999;
29+
}

fixtures/ssr/src/components/Chrome.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import React, {Component} from 'react';
22

3+
import Theme, {ThemeToggleButton} from './Theme';
4+
35
import './Chrome.css';
46

57
export default class Chrome extends Component {
8+
state = {theme: 'light'};
69
render() {
710
const assets = this.props.assets;
811
return (
@@ -14,13 +17,18 @@ export default class Chrome extends Component {
1417
<link rel="stylesheet" href={assets['main.css']} />
1518
<title>{this.props.title}</title>
1619
</head>
17-
<body>
20+
<body className={this.state.theme}>
1821
<noscript
1922
dangerouslySetInnerHTML={{
2023
__html: `<b>Enable JavaScript to run this app.</b>`,
2124
}}
2225
/>
23-
{this.props.children}
26+
<Theme.Provider value={this.state.theme}>
27+
{this.props.children}
28+
<div>
29+
<ThemeToggleButton onChange={theme => this.setState({theme})} />
30+
</div>
31+
</Theme.Provider>
2432
<script
2533
dangerouslySetInnerHTML={{
2634
__html: `assetManifest = ${JSON.stringify(assets)};`,

fixtures/ssr/src/components/Page.css

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1-
.bold {
1+
.link {
22
font-weight: bold;
3+
cursor: pointer;
4+
}
5+
.light-box {
6+
margin: 10px 0;
7+
padding: 10px;
8+
background-color: #CCCCCC;
9+
color: #333333;
10+
}
11+
.dark-box {
12+
margin: 10px 0;
13+
padding: 10px;
14+
background-color: #333333;
15+
color: #CCCCCC;
316
}

fixtures/ssr/src/components/Page.js

+14-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import React, {Component} from 'react';
22

3+
import Theme from './Theme';
4+
import Suspend from './Suspend';
5+
36
import './Page.css';
47

58
const autofocusedInputs = [
@@ -14,17 +17,22 @@ export default class Page extends Component {
1417
};
1518
render() {
1619
const link = (
17-
<a className="bold" onClick={this.handleClick}>
20+
<a className="link" onClick={this.handleClick}>
1821
Click Here
1922
</a>
2023
);
2124
return (
22-
<div>
23-
<p suppressHydrationWarning={true}>A random number: {Math.random()}</p>
24-
<p>Autofocus on page load: {autofocusedInputs}</p>
25-
<p>{!this.state.active ? link : 'Thanks!'}</p>
26-
{this.state.active && <p>Autofocus on update: {autofocusedInputs}</p>}
25+
<div className={this.context + '-box'}>
26+
<Suspend>
27+
<p suppressHydrationWarning={true}>
28+
A random number: {Math.random()}
29+
</p>
30+
<p>Autofocus on page load: {autofocusedInputs}</p>
31+
<p>{!this.state.active ? link : 'Thanks!'}</p>
32+
{this.state.active && <p>Autofocus on update: {autofocusedInputs}</p>}
33+
</Suspend>
2734
</div>
2835
);
2936
}
3037
}
38+
Page.contextType = Theme;

fixtures/ssr/src/components/Page2.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React, {useContext} from 'react';
2+
3+
import Theme from './Theme';
4+
import Suspend from './Suspend';
5+
6+
import './Page.css';
7+
8+
export default function Page2() {
9+
let theme = useContext(Theme);
10+
return (
11+
<div className={theme + '-box'}>
12+
<Suspend>Content of a different page</Suspend>
13+
</div>
14+
);
15+
}
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
let promise = null;
2+
let isResolved = false;
3+
4+
export default function Suspend({children}) {
5+
// This will suspend the content from rendering but only on the client.
6+
// This is used to demo a slow loading app.
7+
if (typeof window === 'object') {
8+
if (!isResolved) {
9+
if (promise === null) {
10+
promise = new Promise(resolve => {
11+
setTimeout(() => {
12+
isResolved = true;
13+
resolve();
14+
}, 6000);
15+
});
16+
}
17+
throw promise;
18+
}
19+
}
20+
return children;
21+
}

fixtures/ssr/src/components/Theme.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React, {createContext, useContext, useState} from 'react';
2+
3+
const Theme = createContext('light');
4+
5+
export default Theme;
6+
7+
export function ThemeToggleButton({onChange}) {
8+
let theme = useContext(Theme);
9+
let [targetTheme, setTargetTheme] = useState(theme);
10+
function toggleTheme() {
11+
let newTheme = theme === 'light' ? 'dark' : 'light';
12+
// High pri, responsive update.
13+
setTargetTheme(newTheme);
14+
// Perform the actual theme change in a separate update.
15+
setTimeout(() => onChange(newTheme), 0);
16+
}
17+
if (targetTheme !== theme) {
18+
return 'Switching to ' + targetTheme + '...';
19+
}
20+
return (
21+
<a className="link" onClick={toggleTheme}>
22+
Switch to {theme === 'light' ? 'Dark' : 'Light'} theme
23+
</a>
24+
);
25+
}

fixtures/ssr/src/index.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
2-
import {hydrate} from 'react-dom';
2+
import {unstable_createRoot} from 'react-dom';
33

44
import App from './components/App';
55

6-
hydrate(<App assets={window.assetManifest} />, document);
6+
let root = unstable_createRoot(document, {hydrate: true});
7+
root.render(<App assets={window.assetManifest} />);

0 commit comments

Comments
 (0)