Swizec Teller - a geek with a hatswizec.com

    Towards a Gatsby+Suspense proof-of-concept

    Messing around with React Suspense gave me a great idea: What if you could fallback to preloaded data instead of a loading spinner? 💡

    K6USjWa

    You could leverage Gatsby's approach with React Suspense for a smooth user experience. Load partial data at build time, bake it into the static HTML, then load the rest when a user renders your page.

    Initial experiments look great, where I'm stuck is loading data for subsequent pages. Let me explain

    You can see it in action here 👉 https://gatsby-suspense-poc.now.sh/

    Code on GitHub 👉 https://github.com/Swizec/gatsby-suspense-poc

    Gatsby with dynamic data, some background

    Let's start with background on using Gatsby for dynamic pages.

    Gatsby is a static site builder. You build a website in React, use GraphQL to connect to data sources, and compile a static HTML page.

    Your GraphQL queries run at compile time and bake data into your static HTML via hardcoded props. Not quite hardcoded props but close enough.

    The result is a page that can work offline and loads without API requests. This makes it fast.

    When your site loads in a browser, however, it becomes a React single page app. React hydration means there's no flash of blank content and no need to re-render the HTML.

    You can add Apollo Client for GraphQL support or use fetch(). It's just a JavaScript app at this point.

    I like to wrap the root node in ApolloProvider and use react-apollo-hooks for the rest.

    Click through for source
    Click through for source

    That gives my entire app access to GraphQL for a specific server. Using Star Wars API for this example.

    Click through for source
    Click through for source

    Loading additional data on render

    Once you have "cached" data baked into your HTML and the ability to load fresh data on render, you're presented with a dilemma.

    How do I show static data, communicate that more data is loading, and show fresh data when I've got it? If the user is offline, what then?

    Those are tough problems. Traditionally, you'd render a component, show a spinner, fetch more data after render, then re-render once done.

    Something like this (this is pseudocode):

    Click through for source
    Click through for source

    STARSHIP_QUERY is a GraphQL query that returns a list of $count starships from StarWarsApi, swapi for short.

    useQuery runs the query and returns a loading state and data.

    Component renders the loading notification and a list of starships from props. Those were baked in at build time. When fresh data arrives from our API call, the component re-renders.

    You can tell this is pseudocode because I'm re-defining the data constant and that won't work :)

    Using React Suspense instead

    With React Suspense, you can make that API call preemptively. While the page is loading.

    Some background on how that works in last week's Experimenting with the new React Concurrent mode article ✌️

    Here's how you might set it up for Gatsby. Or at least how I did.

    Click through for source
    Click through for source

    All this goes in the index.js page. The first page you see in a Gatsby app.

    We start with a static page query for Gatsby. This one runs at build-time and bakes data into our HTML. I tried having a shared query between Gatsby and Apollo, but the graphql (gatsby) and gql (apollo) tags are not compatible.

    Then we've got the starshipQuerySuspender, which runs in the browser when Gatsby turns this page into a JavaScript app.

    The <Starships> component suspends with a data load and renders <StarshipsList> when data shows up. The static page is going to render this same component with its data.

    Page render with suspendering

    In the IndexPage component we can then use <Suspense> to coordinate loading and error states.

    Click through for source
    Click through for source

    Our Gatsby page gets static data via the data prop. This works offline too, you can try, here 👌

    Here's where the fun starts:

    Suspense sees that we're running a GraphQL query (the suspender.read()) so it renders the fallback render prop. But that prop uses our static data to render a list of 5 starships. It's not all of them, but it's better than nothing.

    1tK5XvC

    Even if it all goes to shit, our user gets the most important data they're looking for.

    Once our query finishes, Suspense renders the main list. Now with fresh data from the API.

    29zAunt

    That <ErrorBoundary> gives us an extra feature: We can show something useful even if the user is offline and our query fails.

    vKX8As1

    Same fallback to static data, now with a warning that you're offline ✌️

    Where I'm stuck: subsequent pages

    Now that approach works great, if your site has a single page. Where it breaks down are multi-page sites. Which is most of them.

    I tried replicating the same code on page-2.js and this happens:

    JUHvLjC

    Two swapi requests on initial page load. Makes subsequent pages crazy fast with no hint of data loading, but means that you're loading data for your whole website on initial load.

    That just won't do. We need to hook into the router's lifecycle. Gatsby uses @reach/router by the way.

    And that's where I'm stuck. Tried a few things, none worked.

    Suspend on render, nope

    Loading on render was slow as heck:

    Click through for source
    Click through for source

    Worked but just as slow as running a query without Suspense.

    Reach Router lets you pass state to pages with a state prop in your <Link> component. Whatever you pass shows up in the location prop on the target component.

    Works great for static values, but did not work with this approach:

    Click through for source
    Click through for source

    Not sure why, but trying to pass a function made the whole state value null.

    Hook into onPreRouteUpdate, nyet

    Another approach I tried was using Gatsby's browser APIs. You can hook into various parts of the page lifecycle with special methods in gatsby-browser.js.

    Click through for source
    Click through for source

    Thought I could manipulate the location prop passed into a page and I can, sort of.

    When you console.log that value, it shows that you successfully passed the dataSuspender. This was great news until I realized that data shows up after the page renders.

    How do I know?

    Because console.log(location) shows the value, but console.log(location.dataSuspender) prints undefined. Means the value isn't there when you're printing, but does show up later. Hooray for console.log being dynamic and always showing current object state.

    I tried

    I_tried giphy

    I tried everything I could think of. Some of this stuff is either a bug in Gatsby, a bug in @reach/router, or just not meant to work this way by design.

    Next step: bug people online to find out 😛

    Happy Monday, ~Swizec

    Did you enjoy this article?

    Published on November 11th, 2019 in Front End, Technical,

    Learned something new?
    Read more Software Engineering Lessons from Production

    I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.

    Subscribe below 👇

    Software Engineering Lessons from Production

    Join Swizec's Newsletter and get insightful emails 💌 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 15,883+ engineers learning lessons from my "raw and honest from the heart" emails.

    ⭐️⭐️⭐️⭐️✨
    4.5 stars average rating

    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

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

    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 ❤️