Swizec Teller - a geek with a hatswizec.com

    React 18 and the future of async data

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

    Swizec Teller published ServerlessHandbook.dev avatarSwizec Teller published ServerlessHandbook.dev@Swizec
    It just hit me that idiomatic <Suspense> for data fetching means there's no async, await, or promise anywhere. Shit just works 🀯

    this is huge

    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

    Did you enjoy this article?

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

    Learned something new?
    Want to become an expert?

    Here's how it works πŸ‘‡

    Leave your email and I'll send you thoughtfully written emails every week about React, JavaScript, and your career. Lessons learned over 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths.

    Join Swizec's Newsletter

    And get thoughtful letters πŸ’Œ on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.

    "Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. πŸ‘Œ"

    ~ Ashish Kumar

    Join over 14,000 engineers just like you already improving their careers with my letters, workshops, courses, and talks. ✌️

    Have a burning question that you think I can answer?Β I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

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

    Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

    Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first πŸ’° on the side with ServerlessReact.Dev

    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 bySwizecwith ❀️