Summary: "Suspense for Data Fetching (Experimental)"

chantastic - Nov 1 '19 - - Dev Community

This is a summary of key points in this reactjs.org doc

What Is Suspense, Exactly?

  • Suspense let's you "wait" on loading
  • Suspense is more mechanism than API
  • Suspense is not a data fetching library

This is how facebook currently sees integration: Relay-Suspense integration.

Over time, Suspense will be React's primary way to read asynchronous data — indifferent to source.

What Suspense Is Not

  • Not a date fetching implementation
  • Not a ready-to-use client
  • Does not couple data fetching to your view layer

What Suspense Lets You Do

  • Let's data fetching libraries integrate with React
  • Let's you orchestrate intentionally designed loading states
  • Helps you avoid race conditions

Using Suspense in Practice

Read the Relay Guide to see how facebook interprets data-Suspense integration.

A minimal, "fake" data wrapper for demos:

function wrapPromise(promise) {
  let status = "pending"
  let result
  let suspender = promise.then(
    r => {
      status = "success"
      result = r
    },
    e => {
      status = "error"
      result = e
    }
  )
  return {
    read() {
      if (status === "pending") {
        throw suspender
      } else if (status === "error") {
        throw result
      } else if (status === "success") {
        return result
      }
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

Dan says not to use it but for demos.

What If I Don’t Use Relay?

Wait. You're favorite data fetching lib will likely see Suspense support soon.
Or write your own — for now.

For Library Authors

Example:

function fetchProfileData() {
  let userPromise = fetchUser()
  let postsPromise = fetchPosts()
  return {
    // wrapPromise code is above
    user: wrapPromise(userPromise),
    posts: wrapPromise(postsPromise),
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Suspense is not currently intended as a way to start fetching data when a component renders
  • It lets components express that they’re “waiting” for data that is already being fetched
  • Prefer APIs that favor or enforce fetching before render — to avoid waterfalls

React Core Team recommendations on this will change over time as the space gets fleshed out.

We can create a special "resource" for these complex cases.

This allows React to render components as requisite data becomes available.

As more data streams in, React will retry rendering, and each time it might be able to progress “deeper”

<Suspense /> boundaries give us control over which parts of the page are rendered together or indifferent of each other.

Traditional Approaches vs Suspense

  • Fetch-on-render (for example, fetch in useEffect)
    • components that fetch data and render components that fetch data can lead to "waterfalls"
  • Fetch-then-render (for example, Relay without Suspense)
    • IO blocks render
  • Render-as-you-fetch (for example, Relay with Suspense)
    • Fetch and render simultaneously. Render what's available as it streams in. But on a schedule (later concept)

Applications will use a blend of these strategies.

Approach 1: Fetch-on-Render (not using Suspense)

Example:

useEffect(() => {
  fetchSomething()
}, [])
Enter fullscreen mode Exit fullscreen mode

"Waterfalls" are when one component fetches and then a rendered child component fetches.
The second won't start until the first is resolved.

Approach 2: Fetch-Then-Render (not using Suspense)

Example:

function fetchProfileData() {
  return Promise.all([fetchUser(), fetchPosts()]).then(([user, posts]) => {
    return { user, posts }
  })
}
Enter fullscreen mode Exit fullscreen mode

These are fetched in parallel.
But because of Promise.all, rendering is blocked by the longest query.

Approach 3: Render-as-You-Fetch (using Suspense)

  • Start fetching
  • Start rendering
  • Finish fetching

With Suspense, we don’t wait for the response to come back before we start rendering.

We start rendering immediately.

We’re Still Figuring This Out

Suspense — itself — is flexible.

Ongoing questions:

  • How do we make it easier to avoid waterfalls?
  • When we fetch data for a page, can the API encourage including data for instant transitions from it?
  • What is the lifetime of a response? Should caching be global or local? Who manages the cache?
  • Can Proxies help express lazy-loaded APIs without inserting read() calls everywhere?
  • What would the equivalent of composing GraphQL queries look like for arbitrary Suspense data?

Suspense and Race Conditions

Race conditions: a bug that happens due to incorrect assumptions about the order in which our code may run.

Race Conditions with useEffect

In this example, previous requests can resolve after the latest request and clobber the current state.

Providing a useEffect cleanup function that cancels or ignores the previous request could fix this but requires vigilance.

Race Conditions with componentDidUpdate

Same problem.
More code.
Harder to think about.

The Problem

Problem: "synchronizing" several processes that affect each other is the problem.

Solving Race Conditions with Suspense

Example Sandbox

  • Set state immediately
  • Pass resource (containing our wrapped promises) to the component for immediate rendering
  • Let Suspense boundaries decide when and what to render

Handling Errors

Error boundaries like other React code.

ErrorBoundary MVP:

class ErrorBoundary extends React.Component {
  state = { hasError: false, error: null }
  static getDerivedStateFromError(error) {
    return {
      hasError: true,
      error,
    }
  }
  render() {
    if (this.state.hasError) {
      return this.props.fallback
    }
    return this.props.children
  }
}
Enter fullscreen mode Exit fullscreen mode

Be intentional about ErrorBoundary placement:

The Fault in Our Tolerance: Accounting for Failures in React

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .