Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partial Hydration #14717

Merged
merged 21 commits into from
Feb 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
bc3e2f9
Basic partial hydration test
sebmarkbage Jan 18, 2019
433e6e5
Render comments around Suspense components
sebmarkbage Jan 19, 2019
2d28a52
Add DehydratedSuspenseComponent type of work
sebmarkbage Jan 24, 2019
f06a540
Add comment node as hydratable instance type as placeholder for suspense
sebmarkbage Jan 24, 2019
97a6664
Skip past nodes within the Suspense boundary
sebmarkbage Jan 28, 2019
1b3e0a2
A dehydrated suspense boundary comment should be considered a sibling
sebmarkbage Jan 28, 2019
6febcae
Retry hydrating at offscreen pri or after ping if suspended
sebmarkbage Jan 28, 2019
7af0b8a
Enter hydration state when retrying dehydrated suspense boundary
sebmarkbage Jan 28, 2019
dac0688
Delete all children within a dehydrated suspense boundary when it's d…
sebmarkbage Jan 28, 2019
92fb62a
Delete server rendered content when props change before hydration com…
sebmarkbage Jan 29, 2019
6ae914c
Make test internal
sebmarkbage Jan 29, 2019
e144a5e
Wrap in act
sebmarkbage Feb 10, 2019
eb3ea2d
Change SSR Fixture to use Partial Hydration
sebmarkbage Feb 9, 2019
97eb545
Changes to any parent Context forces clearing dehydrated content
sebmarkbage Feb 10, 2019
c91092b
Wrap in feature flag
sebmarkbage Feb 11, 2019
2e16dc1
Treat Suspense boundaries without fallbacks as if not-boundaries
sebmarkbage Feb 12, 2019
1db9dd2
Fix clearing of nested suspense boundaries
sebmarkbage Feb 12, 2019
3a89f65
ping -> retry
acdlite Feb 12, 2019
e0e1ded
Typo
acdlite Feb 12, 2019
ca2d628
Use didReceiveUpdate instead of manually comparing props
sebmarkbage Feb 12, 2019
34a132c
Leave comment for why it's ok to ignore the timeout
sebmarkbage Feb 12, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions fixtures/ssr/src/components/App.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
import React, {Component} from 'react';
import React, {useContext, useState, Suspense} from 'react';

import Chrome from './Chrome';
import Page from './Page';
import Page2 from './Page2';
import Theme from './Theme';

export default class App extends Component {
render() {
return (
<Chrome title="Hello World" assets={this.props.assets}>
<div>
<h1>Hello World</h1>
<Page />
</div>
</Chrome>
);
}
function LoadingIndicator() {
let theme = useContext(Theme);
return <div className={theme + '-loading'}>Loading...</div>;
}

export default function App({assets}) {
let [CurrentPage, switchPage] = useState(() => Page);
return (
<Chrome title="Hello World" assets={assets}>
<div>
<h1>Hello World</h1>
<a className="link" onClick={() => switchPage(() => Page)}>
Page 1
</a>
{' | '}
<a className="link" onClick={() => switchPage(() => Page2)}>
Page 2
</a>
<Suspense fallback={<LoadingIndicator />}>
<CurrentPage />
</Suspense>
</div>
</Chrome>
);
}
24 changes: 24 additions & 0 deletions fixtures/ssr/src/components/Chrome.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,27 @@ body {
padding: 0;
font-family: sans-serif;
}

body.light {
background-color: #FFFFFF;
color: #333333;
}

body.dark {
background-color: #000000;
color: #CCCCCC;
}

.light-loading {
margin: 10px 0;
padding: 10px;
background-color: #CCCCCC;
color: #666666;
}

.dark-loading {
margin: 10px 0;
padding: 10px;
background-color: #333333;
color: #999999;
}
12 changes: 10 additions & 2 deletions fixtures/ssr/src/components/Chrome.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import React, {Component} from 'react';

import Theme, {ThemeToggleButton} from './Theme';

import './Chrome.css';

export default class Chrome extends Component {
state = {theme: 'light'};
render() {
const assets = this.props.assets;
return (
Expand All @@ -14,13 +17,18 @@ export default class Chrome extends Component {
<link rel="stylesheet" href={assets['main.css']} />
<title>{this.props.title}</title>
</head>
<body>
<body className={this.state.theme}>
<noscript
dangerouslySetInnerHTML={{
__html: `<b>Enable JavaScript to run this app.</b>`,
}}
/>
{this.props.children}
<Theme.Provider value={this.state.theme}>
{this.props.children}
<div>
<ThemeToggleButton onChange={theme => this.setState({theme})} />
</div>
</Theme.Provider>
<script
dangerouslySetInnerHTML={{
__html: `assetManifest = ${JSON.stringify(assets)};`,
Expand Down
15 changes: 14 additions & 1 deletion fixtures/ssr/src/components/Page.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
.bold {
.link {
font-weight: bold;
cursor: pointer;
}
.light-box {
margin: 10px 0;
padding: 10px;
background-color: #CCCCCC;
color: #333333;
}
.dark-box {
margin: 10px 0;
padding: 10px;
background-color: #333333;
color: #CCCCCC;
}
20 changes: 14 additions & 6 deletions fixtures/ssr/src/components/Page.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import React, {Component} from 'react';

import Theme from './Theme';
import Suspend from './Suspend';

import './Page.css';

const autofocusedInputs = [
Expand All @@ -14,17 +17,22 @@ export default class Page extends Component {
};
render() {
const link = (
<a className="bold" onClick={this.handleClick}>
<a className="link" onClick={this.handleClick}>
Click Here
</a>
);
return (
<div>
<p suppressHydrationWarning={true}>A random number: {Math.random()}</p>
<p>Autofocus on page load: {autofocusedInputs}</p>
<p>{!this.state.active ? link : 'Thanks!'}</p>
{this.state.active && <p>Autofocus on update: {autofocusedInputs}</p>}
<div className={this.context + '-box'}>
<Suspend>
<p suppressHydrationWarning={true}>
A random number: {Math.random()}
</p>
<p>Autofocus on page load: {autofocusedInputs}</p>
<p>{!this.state.active ? link : 'Thanks!'}</p>
{this.state.active && <p>Autofocus on update: {autofocusedInputs}</p>}
</Suspend>
</div>
);
}
}
Page.contextType = Theme;
15 changes: 15 additions & 0 deletions fixtures/ssr/src/components/Page2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React, {useContext} from 'react';

import Theme from './Theme';
import Suspend from './Suspend';

import './Page.css';

export default function Page2() {
let theme = useContext(Theme);
return (
<div className={theme + '-box'}>
<Suspend>Content of a different page</Suspend>
</div>
);
}
21 changes: 21 additions & 0 deletions fixtures/ssr/src/components/Suspend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
let promise = null;
let isResolved = false;

export default function Suspend({children}) {
// This will suspend the content from rendering but only on the client.
// This is used to demo a slow loading app.
if (typeof window === 'object') {
if (!isResolved) {
if (promise === null) {
promise = new Promise(resolve => {
setTimeout(() => {
isResolved = true;
resolve();
}, 6000);
});
}
throw promise;
}
}
return children;
}
25 changes: 25 additions & 0 deletions fixtures/ssr/src/components/Theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, {createContext, useContext, useState} from 'react';

const Theme = createContext('light');

export default Theme;

export function ThemeToggleButton({onChange}) {
let theme = useContext(Theme);
let [targetTheme, setTargetTheme] = useState(theme);
function toggleTheme() {
let newTheme = theme === 'light' ? 'dark' : 'light';
// High pri, responsive update.
setTargetTheme(newTheme);
// Perform the actual theme change in a separate update.
setTimeout(() => onChange(newTheme), 0);
}
if (targetTheme !== theme) {
return 'Switching to ' + targetTheme + '...';
}
return (
<a className="link" onClick={toggleTheme}>
Switch to {theme === 'light' ? 'Dark' : 'Light'} theme
</a>
);
}
5 changes: 3 additions & 2 deletions fixtures/ssr/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import {hydrate} from 'react-dom';
import {unstable_createRoot} from 'react-dom';

import App from './components/App';

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