Swizec Teller - a geek with a hatswizec.com

Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Senior Engineer Mindset cover
Learn more

    React 18 and the future of async data

    Friend, I have glimpsed the future and it is amazing.

    React 18 is shipping with Suspense and startTransition for deferred component rendering, but not data loading. That's coming in a future 18.x version.

    To explore that future, I built a side-by-side comparison of current best practice – React Query – and future Suspense for data fetching. Using the latest experimental version of React.

    PS: I'll be talking about this stuff at React Dinner, an in-person event, next week.

    What you see in this demo

    The demo shows a New York Times best seller list.

    Its name and publication date come from an API call. The list of books comes from another API call. You get a cascading spinner effect. Need data from the first call to make the second.

    Gif of the demo

    Suspense coordinates those loading states for you and shows 1 spinner. Nothing renders until everything's ready. 😍

    But the true benefit is how Suspense simplifies your code.

    You're doing extra work right now

    Look at the WithoutSuspense branch of my demo. There's a lot of unnecessary fluff in there.

    Low-level fetching

    First you have the low-level data loading. An async helper that calls the fetch() method to load data from an API.

    export async function fetchBookLists() {
      const res = await fetch(`
      https://api.nytimes.com/svc/books/v3/lists/names.json?api-key=${API_KEY}`)
    
      const json = await res.json()
    
      if (json.status === "OK") {
        return json.results
      } else {
        console.log(json)
        throw new Error("Loading failed, likely rate limit")
      }
    }
    

    Async function, a bunch of awaits. You await the fetch(), then you await the json() parsing, then you return the result or throw an error.

    Libraries like Axios make this part easier, but not much. I never found them worth the extra JavaScript. 🤷‍♂️

    Hooks for data loading

    Second you have a helper hook that loads your data. You should use React Query or similar for this part.

    A basic implementation looks like this:

    // fetches NYT best seller lists
    function useNYTBestSellerLists() {
      // poor man's useQuery implementation
      const [isLoading, setIsLoading] = useState(false)
      const [lists, setLists] = useState(null)
    
      useEffect(() => {
        setIsLoading(true)
    
        fetchBookLists()
          .then((lists) => {
            setLists(lists)
            setIsLoading(false)
          })
          .catch(() => setIsLoading(false))
      }, [])
    
      return { isLoading, lists }
    }
    

    State for isLoading and lists (data). An effect runs on component mount, sets isLoading to true, asynchronously loads your data, then updates state.

    You can use this query for any component that needs a list of best sellers lists.

    To avoid re-fetching the same data for every component, libraries like React Query and ApolloGraphQL use an internal global cache. The shared cache leads to ridiculously snappy UI.

    Feels like your app's broken sometimes because it's so fast 😁

    Dealing with async data in components

    Third you have to handle loading states everywhere.

    export const BestSellers = () => {
      const { isLoading, lists } = useNYTBestSellerLists();
    
      if (isLoading) {
        return <Spinner />;
      }
    
      if (!lists) {
        return "not loading or error";
      }
    
      const list = lists[0];
    
      return (
        <>
          <h4>From {list.display_name}</h4>
          <Paragraph sx={{ mt: -3 }}>
            Published on {list.newest_published_date}
          </Paragraph>
          <BookList list={list} />
        </>
      );
    

    Run the query, show <Spinner> while loading, render your component when data becomes available.

    Every component that depends on a data query grows this fuzzy little workaround. You never know when a cache might expire.

    You can shove all your data loading into parent components and keep renders pure, but that leads to even fuzzier code in practice.

    Load data where you use it. Let React Query coordinate.

    The future with Suspense

    Suspense turns async states into first-class citizens of React. You don't have to think about it. At all 🤯

    Took me a while to grok this.

    You'll need React 18.x and a suspense-enabled library like react-fetch. The library would rely on suspense <Cache> internally. All of this is experimental, not even alpha.

    Here's what the future looks like:

    All the fiddly stuff from before melts away.

    Low-level fetching

    No more async in your low-level fetches.

    import { fetch } from "react-fetch"
    
    export function fetchBookLists() {
      const res = fetch(`
      https://api.nytimes.com/svc/books/v3/lists/names.json?api-key=${API_KEY}`)
    
      const json = res.json()
    
      if (json.status === "OK") {
        return json.results
      } else {
        console.log(json)
        throw new Error("Loading failed, likely rate limit")
      }
    }
    

    Fetch data from an API, parse the json, return the result or throw an error. No async or await in sight.

    Show loading states

    You use a Suspense component to show loading states.

    export const BestSellers = () => {
      return (
        <Suspense fallback={<Spinner />}>
          {/* loading must happen inside a Suspense */}
          <Content />
        </Suspense>
      )
    }
    

    Any component inside Suspense can say "Halt! Don't render me yet". React waits until every "halt" is resolved to render the children.

    That's true for sibling components as well!

    <Suspense fallback={...}>
      <ComponentThatLoadsData />
      <PlainSibling />
    </Suspense>
    

    <PlainSibling> won't render until <ComponentThatLoadsData> is ready. Its effects won't run either. ✌️

    While components resolve, React shows the fallback.

    No async state in components

    And here's the best part – no more async state 🤯

    // We never have to notice data loading is async
    const Content = () => {
      const list = fetchBookLists()[0]
    
      return (
        <>
          <h4>From {list.display_name}</h4>
          <Paragraph sx={{ mt: -3 }}>
            Published on {list.newest_published_date}
          </Paragraph>
          <BookList list={list} />
        </>
      )
    }
    

    The component fetches best seller lists from an API. Zero consideration for async loading.

    No spinner states, no "list may be undefined", nothing. Just plain ol' JavaScript.

    It honestly looks like magic. I'm almost afraid to dig into how the heck they achieved this.

    What do you think? I for one can't wait to start deleting half my code 😇

    Cheers,
    ~Swizec

    PS: if you're in town, next week's React Dinner about React 18 is almost sold out

    Published on July 22nd, 2021 in React, Frontend, Technical

    Did you enjoy this article?

    Continue reading about React 18 and the future of async data

    Semantically similar articles hand-picked by GPT-4

    Senior Mindset Book

    Get promoted, earn a bigger salary, work for top companies

    Learn more

    Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.

    Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.

    Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.

    Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev

    Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization

    Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections

    Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog

    Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

    By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️

    Created by Swizec with ❤️