buffering..
💩
React Suspense Cheat Sheet
Benefits/Uses ⚒️
Goals 📊
Code 👩💻
💩
New Primitives
-
React.Timeout
-
React.AsyncMode
Low Level API
-
simple-cache-provider
- simple-cache-provider.SimpleCache
- simple-cache-provider.createResource
- Community will write more, e.g. Apollo/Relay
Low Level API
type Cache = {
invalidate(): void,
read<K, V, A>(
resourceType: mixed,
key: K,
miss: (A) => Promise<V>,
missArg: A,
): V,
preload<K, V, A>(
resourceType: mixed,
key: K,
miss: (A) => Promise<V>,
missArg: A,
): void,
// DEV-only
$$typeof?: Symbol | number,
};
Low Level API
read<K, V, A>(
resourceType: any,
key: K,
miss: A => Promise<V>,
missArg: A,
): V {
const record: Record<V> = getRecord(resourceType, key);
switch (record.status) {
case Empty:
// Load the requested resource.
const suspender = miss(missArg);
load(record, suspender);
throw suspender;
case Pending:
// There's already a pending request.
throw record.suspender;
case Resolved:
return record.value;
case Rejected:
default:
// The requested resource previously failed loading.
const error = record.error;
throw error;
}
},
High Level API
-
Combine Cache + Primitives
import { default as React, Timeout } from "react";
import { unstable_deferredUpdates } from "react-dom";
import { createResource, createCache } from "simple-cache-provider";
const cache = createCache(() => {});
export function createFetcher(resolver) {
const resource = createResource(resolver);
return {
read: key => resource.read(cache, key)
};
}
export function Placeholder(props) {
return (
<Timeout ms={props.delayMs}>
{didExpire => (didExpire ? props.fallback : props.children)}
</Timeout>
);
}
export class Component extends React.Component {
deferSetState(state) {
unstable_deferredUpdates(() => this.setState(state));
}
}
export class Loading extends React.Component {
state = { isLoading: false };
componentDidCatch(fetcher) {
fetcher.then(() => this.setState({ isLoading: false }));
this.setState({ isLoading: true });
}
render() {
return this.props.children(this.state.isLoading);
}
}
React Suspense Cheat Sheet
Benefits/Uses ⚒️
Goals 📊
Code 👩💻
Primitives
- React.Timeout⭐
- React.AsyncMode
Low Level API
- simple-cache-provider
High Level API (../future)
- createFetcher()
- <Placeholder delayMs>
- <Loading />
- this.deferSetState()
💩
Benefits/Use Cases
What can we do with it?
Goals
What are React's priorities?
What are the big ideas?
Data Fetching
Tearing = Unacceptable
Data Fetching
Why do we show spinners by default?
SPINNERS = TEARING
React Suspense Cheat Sheet
Benefits/Uses ⚒️
Goals 📊
Code 👩💻
💩
Better Defaults: Data
IndexPage
MoviePage
MoviePage
Movie
Details
vs
IndexPage
MoviePage
Movie
Details
IndexPage
onClick
cDM -> fetch
onClick -> fetch
fetch resolves
interactive
useless!
Data Fetching
const movieReviewsFetcher = createFetcher(fetchMovieReviews);
function MovieReviews({ movieId }) {
const reviews = movieReviewsFetcher.read(movieId);
return (
<div className="MovieReviews">
{reviews.map(review => <MovieReview key={review.id} {...review} />)}
</div>
);
}
Better Default: Img
IndexPage
MoviePage
MoviePage
Movie
Image
vs
IndexPage
IndexPage
onClick
<img src="">
onClick -> fetch
fetch resolves
no jumps
jumpy!
MoviePage
Movie
Image
interactive
incomplete
Data Fetching: Image Loading
const imageFetcher = createFetcher(
src =>
new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => resolve(src);
image.onerror = reject;
image.src = src;
})
);
function Img(props) {
return <img {...props} src={imageFetcher.read(props.src)} />;
}
(CSS too; goodbye FOUC)
When Image Loading Fails You
Hawaiian Missile Alert Software edition
When Image Loading Fails You
Hawaiian Missile Alert Software edition
Data Fetching: Code Splitting
const moviePageFetcher = createFetcher(() => import("./MoviePage"));
function MoviePageLoader(props) {
const MoviePage = moviePageFetcher.read().default;
return <MoviePage {...props} />;
}
React Suspense Cheat Sheet
Benefits/Uses ⚒️
-
Data Fetching
-
Code splitting
-
Img loading
-
CSS loading
-
Goals 📊
-
Render when ready
-
Fetch in render
Code 👩💻
Primitives
- React.Timeout⭐
- React.AsyncMode
Low Level API
- simple-cache-provider
High Level API (../future)
- createFetcher()
- <Placeholder delayMs>
- <Loading />
- this.deferSetState()
💩
Better Default: Race Cond. 1
IndexPage
vs
onClick -> fetch
🤷🏼♂️
MoviePage 2
MoviePage 1
IndexPage
onClick -> fetch
👨🏼🎓
MoviePage 2
MoviePage 1
Fix Race Conditions
<Placeholder delayMs={1500} fallback={<AppSpinner />}>
{!showDetail ? (
<IndexPage
loadingMovieId={currentMovieId}
onMovieClick={this.handleMovieClick}
/>
) : (
<MoviePageLoader movieId={currentMovieId} />
)}
</Placeholder>
In high latency, MoviePageLoader does not display until all children have the data
Meanwhile IndexPage is still interactive. If a new movie is selected, MoviePageLoader never shows the old one
IndexPage
vs
IndexPage
IndexPage
onClick
onClick
interactive
incomplete
Better Default: Race Cond. 2
MovieReviews
MovieDetails
MovieReviews
MovieReviews
MovieDetails
MovieReviews
Fix Race Conditions
export default function MoviePage({ movieId }) {
return (
<>
<MovieDetails movieId={movieId} />
<MovieReviews movieId={movieId} />
</>
);
}
Guaranteed to display together... by default
Fix Race Conditions
function Delay({ms}) {
return (
<Timeout ms={ms}>
{didTimeout => didTimeout ? null : <Never />}
</Timeout>
);
}
function DebouncedText({text, ms}) {
return (
<Fragment>
<Delay ms={ms} />
<Text text={text} />
</Fragment>
);
}
Debounce is trivial - goodbye lodash
React Suspense Cheat Sheet
Benefits/Uses ⚒️
-
Data Fetching
-
Code splitting
-
Img loading
-
CSS loading
-
-
Fix race conditions
-
Debouncing
Goals 📊
-
Render when ready
-
Fetch in render
-
No race default
Code 👩💻
Primitives
- React.Timeout⭐
- React.AsyncMode
Low Level API
- simple-cache-provider
High Level API (../future)
- createFetcher()
- <Placeholder delayMs>
- <Loading />
- this.deferSetState()
💩
But I want Spinners 1
shame on you
but ok
IndexPage
onClick
MovieDetails
MovieReviews
MovieDetails
Intentional States
aka Precise Control
export default function MoviePage({ movieId }) {
return (
<>
<MovieDetails movieId={movieId} />
<MovieReviews movieId={movieId} />
</>
);
}
export default function MoviePage({ movieId }) {
return (
<>
<MovieDetails movieId={movieId} />
<Placeholder delayMs={500} fallback={<Spinner />}>
<MovieReviews movieId={movieId} />
</Placeholder>
</>
);
}
vs.
But I want Spinners 2
IndexPage
IndexPage
onClick
interactive
MovieReviews
MovieDetails
MovieReviews
Intentional States
show that you're loading
<Placeholder delayMs={1500} fallback={<AppSpinner />}>
{!showDetail ? (
<Loading>
{isLoading => (
<IndexPage
showLoading={isLoading}
loadingMovieId={currentMovieId}
onMovieClick={this.handleMovieClick}
/>
)}
</Loading>
) : (
<MoviePageLoader movieId={currentMovieId} />
)}
</Placeholder>
with the <Loading /> component!
*not a real example
React Suspense Cheat Sheet
Benefits/Uses ⚒️
-
Data Fetching
-
Code splitting
-
Img loading
-
CSS loading
-
-
Fix race conditions
-
Debouncing
-
Intentional States
Goals 📊
-
Render when ready
-
Fetch in render
-
No race default
-
Precise control
Code 👩💻
Primitives
- React.Timeout⭐
- React.AsyncMode
Low Level API
- simple-cache-provider
High Level API (../future)
- createFetcher()
- <Placeholder delayMs>
- <Loading />
- this.deferSetState()
💩
(Cheating) Fast UI
with Preloading
IndexPage
onClick -> fetch
MoviePage 2
MoviePage 1
MovieImage 2
Preloading
don't go chasing waterfalls
vs
Preloading
function Img({cache, src, ...props}) {
preloadImg(cache, `https://contacts.now.sh/images/${id}.jpg`)
return <img src={readImage(cache, src)} {...props} />;
}
export default withCache(Img);
<div>
<MoviePageLoader id={id} />
</div>
<div hidden={true}>
<MoviePageLoader id={id + 1} />
</div>
React Suspense Cheat Sheet
Benefits/Uses ⚒️
-
Data Fetching
-
Code splitting
-
Img loading
-
CSS loading
-
-
Fix race conditions
-
Debouncing
-
Intentional States
-
Preloading
Goals 📊
-
Render when ready
-
Fetch in render
-
No race default
-
Precise control
-
Speculative fetch
Code 👩💻
Primitives
- React.Timeout⭐
- React.AsyncMode
Low Level API
- simple-cache-provider
High Level API (../future)
- createFetcher()
- <Placeholder delayMs>
- <Loading />
- this.deferSetState()
💩
Interactive AF
aka React ➡️ Interact
aka React has a new job:
Stay Interactive
(eg 60 fps/16 ms)
aka Why Suspense belongs in React
Misconception
DOM is expensive
VDOM is cheap
New Focus
commit is cheap
reconciliation is expensive
Not hypothetical
vs (source)
NOT AN OFFICIAL DIAGRAM. DONT TAKE LITERALLY
React Suspense Cheat Sheet
Benefits/Uses ⚒️
-
Data Fetching
-
Code splitting
-
Img loading
-
CSS loading
-
-
Fix race conditions
-
Debouncing
-
Intentional States
-
Preloading
Goals 📊
-
Render when ready
-
Fetch in render
-
No race default
-
Precise control
-
Speculative fetch
-
Interactive AF
Glossary 🤖
- Coroutines
- Reconciliation
- Double Buffering
Code 👩💻
Primitives
- React.Timeout⭐
- React.AsyncMode
Low Level API
- simple-cache-provider
High Level API (../future)
- createFetcher()
- <Placeholder delayMs>
- <Loading />
- this.deferSetState()
💩
Innovation
Post-Suspense
- Creative Suspense Use Cases
- Low Level API (eg Cache)
- High Level API (eg ../future, @reactions/Fetch)
- Existing React Libraries
- New Dev tools
- ???
No time for, but worth learning about/exploring
React Suspense Cheat Sheet
Benefits/Uses ⚒️
-
Data Fetching⭐
Code splitting
Img loading
CSS loading
Fix race conditions
Debouncing
Intentional states⭐
Preloading
Less boilerplate
Streaming SSR ⚠️
what else??⭐⭐⭐
Goals 📊
-
Render when ready⭐
-
Fetch in render
-
No race default⭐
-
Precise control⭐
-
Speculative fetch
-
Interactive AF⭐
Glossary 🤖
- Idempotency != Purity
- Algebraic Effects
- Coroutines⭐
- Reconciliation
- Double Buffering
- Expiration ⚠️
Code 👩💻
Primitives
- React.Timeout⭐
- React.AsyncMode
Low Level API
- simple-cache-provider⭐
- Apollo/Relay cache⭐⭐
- what else?? ⭐⭐⭐⭐
High Level API (../future)
- createFetcher()
- <Placeholder delayMs>
- <Loading />
- this.deferSetState()
- <div hidden={true}> ⚠️
Impact on Libraries⭐⭐
New Devtools⭐⭐⭐
by @swyx 🐙
🐙
work in progress
By Shawn Swyx Wang
work in progress
- 2,591