💩
Benefits/Uses ⚒️
Goals 📊
Code 👩💻
💩
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,
};
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;
}
},
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);
}
}
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()
💩
What are the big ideas?
Tearing = Unacceptable
Why do we show spinners by default?
SPINNERS = TEARING
Benefits/Uses ⚒️
Goals 📊
Code 👩💻
💩
IndexPage
MoviePage
MoviePage
Movie
Details
vs
IndexPage
MoviePage
Movie
Details
IndexPage
onClick
cDM -> fetch
onClick -> fetch
fetch resolves
interactive
useless!
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>
);
}
IndexPage
MoviePage
MoviePage
Movie
Image
vs
IndexPage
IndexPage
onClick
<img src="">
onClick -> fetch
fetch resolves
no jumps
jumpy!
MoviePage
Movie
Image
interactive
incomplete
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)
Hawaiian Missile Alert Software edition
Hawaiian Missile Alert Software edition
const moviePageFetcher = createFetcher(() => import("./MoviePage"));
function MoviePageLoader(props) {
const MoviePage = moviePageFetcher.read().default;
return <MoviePage {...props} />;
}
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()
💩
IndexPage
vs
onClick -> fetch
MoviePage 2
MoviePage 1
IndexPage
onClick -> fetch
MoviePage 2
MoviePage 1
<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
MovieReviews
MovieDetails
MovieReviews
MovieReviews
MovieDetails
MovieReviews
export default function MoviePage({ movieId }) {
return (
<>
<MovieDetails movieId={movieId} />
<MovieReviews movieId={movieId} />
</>
);
}
Guaranteed to display together... by default
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
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()
💩
shame on you
but ok
IndexPage
onClick
MovieDetails
MovieReviews
MovieDetails
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.
IndexPage
IndexPage
onClick
interactive
MovieReviews
MovieDetails
MovieReviews
<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
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()
💩
IndexPage
onClick -> fetch
MoviePage 2
MoviePage 1
MovieImage 2
vs
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>
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()
💩
Stay Interactive
(eg 60 fps/16 ms)
vs (source)
NOT AN OFFICIAL DIAGRAM. DONT TAKE LITERALLY
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()
💩
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 🐙
🐙