buffering..

💩

React Suspense Cheat Sheet

Benefits/Uses ⚒️

Goals 📊

Code 👩‍💻

💩

New Primitives

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

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

  • 550
Loading comments...

More from Shawn Swyx Wang