React.Suspense doesn't work with Gatsby (yet)
Photo by Егор Камелев on Unsplash
(Chosen as a featured image because it's a suspense-invoking cute lil' creature 😅)
I was loading components dynamically on Gatsby using React.lazy, which required to use React.Suspense.
But then I got the following message while building the site.
Actually I found out while deploying it on Netlify first 😅 (then ran _gatsby build_
locally)
WebpackError: Invariant Violation: Minified React error #294; visit https://reactjs.org/docs/error-decoder.html?invariant=294 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
Fonts look broken on local machine too by the way
The error message points you to https://reactjs.org/docs/error-decoder.html?invariant=294, which shows that
ReactDOMServer does not yet support Suspense.
Nice clear message (no sarcasm intended).
Gatsby uses ReactDOMServer in the build process thus the error occurred.
Fixing the Offending Code
Below is the offending code using React.lazy causing the issue.
import React, { Suspense, lazy } from "react" | |
import { graphql, useStaticQuery } from "gatsby" | |
import Layout from "../components/Layout" | |
const importBlock = blockName => | |
lazy(() => import(`../components/blocks/${blockName}/index.js`)) | |
export default () => { | |
const { allDirectory } = useStaticQuery(graphql` | |
{ | |
allDirectory(filter: { relativePath: { ne: "" } }) { | |
edges { | |
node { | |
relativePath | |
id | |
} | |
} | |
} | |
} | |
`) | |
const blocks = allDirectory.edges.map(({ node }) => { | |
const Block = importBlock(node.relativePath) | |
return <Block key={node.id} /> | |
}) | |
return ( | |
<Layout> | |
<Suspense fallback={"loading"}>{blocks}</Suspense> | |
</Layout> | |
) | |
} |
Don't do this, yet as it's not supported!
Components are loaded "lazily" on line #6, which caused React.Suspense wrap in the return statement at the bottom.
Lines #28 ~ #30
So to remove Suspense
, get rid of React.lazy
and replace it with a regular dynamic import(), and return a default module.
We need to keep the components loaded in a state, so let's use useState and load it in the useEffect hook.
If you want to use Class Components, refer to case #1 of my other post, Loading React Components Dynamically on Demand, which was written when Hooks weren't available
import React, { useState, useEffect } from "react" | |
import { graphql, useStaticQuery } from "gatsby" | |
import Layout from "../components/Layout" | |
const importBlock = blockName => | |
import(`../components/blocks/${blockName}/index.js`).then( | |
component => component.default | |
) | |
export default () => { | |
const [components, setComponents] = useState([]) | |
const { allDirectory } = useStaticQuery(graphql` | |
{ | |
allDirectory(filter: { relativePath: { ne: "" } }) { | |
edges { | |
node { | |
relativePath | |
id | |
} | |
} | |
} | |
} | |
`) | |
useEffect(() => { | |
function loadComponents() { | |
allDirectory.edges.map(async ({ node }) => { | |
const component = await importBlock(node.relativePath) | |
setComponents(loadedComponents => loadedComponents.concat(component)) | |
}) | |
} | |
loadComponents() | |
}, [allDirectory]) | |
return ( | |
<Layout> | |
{components.map(Component => ( | |
<Component key={Component} /> | |
))} | |
</Layout> | |
) | |
} |
This now builds 🙂
allDirectory
is loaded via a static GraphQL query, and when the directories are loaded, it causes the useEffect
to render.
And loadComponents
(aptly named, right? 😉) loads all components dynamically, and saves it to components
state, which is used within return statement to render.
Regarding _key={Component}_
, I was too "lazy" to come up with a unique key so used an object instead.
Parting Words
As the title shows, I just wanted to point out that Suspense
isn't working with Gatsby, yet.
But I ended up fixing the issue and wrote more soon after.
I am going to keep the "fix" part short as it's already written about in the previous posts already.
If you have a trouble with converting it into using Function Components with hooks, let me know~
Webmentions
Loading counts...