In case you missed it, Gatsby 5 shipped with the new Slice API!
Gatsby’s new Slice API can be used to help speed up common updates across your site. By pulling out common components into separate HTML files, common components can be built separately and “stitched” together with existing pages.
You can read more in the docs here: Gatsby Slice API or, in this excellent blog post by Gatsby’s Product Manager Josh Johnson: The Gatsby Slice API: High Precision Incremental Builds.
As good as the Slice API is, it does come with one or two restrictions. To be clear, these are by design rather than an oversight. One such restriction I ran into was when I attempted to pass a prop of type function to the Slice component.
E.g
Using a Slice in this way will result in an error similar to the below.
Slice “header” was passed props that are not serializable (not serializable “function” type…)
In this blog post I’ll explain why this happens, and one method I used to work round this restriction, but first, a simple demo.
A Simple Theme Toggle Demo
I recently ran into the “function prop” issue myself when attempting to pass a function that controlled a dark/light mode Theme Toggle.
I’ve prepared a minimal example site and repository to better demonstrate the functionality I needed.
The demo site has a Theme Toggle in the Header component. Header components are a prime use case for the Slice API because they’re often used / reused across every page of a site.
The Problem With Function Props
From the very start of the Slice API project the team were focussed on builds with incredible precision, in fact, I have it on good authority that the Epic was actually called “High Precision Incremental Builds”.
In order to achieve the highest precision possible, any props passed to the Slice component need to be serializable. Once serialized they can be stored and used on subsequent builds, or “slotted” into cached HTML pages.
Serializing functions for diff comparison can’t really be achieved in a reliable way because Javascript functions are non primitive types and can’t be serialized in a way that Gatsby could use to perform a diff, or high precision build.
Function Serialization
Here’s a simple data object which I’ll use to demonstrate the issue. It contains a title and a date, both are typeof
string.
If i use JSON.stringify(data, null, 2)
to serialize this object i’d see the following output.
Serialization of primitive data such as Strings is ok because the output can be compared in a reliable and predictable way.
Sadly this is not the case with functions.
Here’s a simple function for demonstration purposes.
Using the same approach as before, JSON.stringify(sum, null, 2)
to serialize this function I’d see the following output
I could, as an extra step, cast the function as a string before serializing it like this, JSON.stringify((""+ sum), null, 2)
, which would output the following.
Or, another option would be to do something like sum.toString()
, which would output the following.
However, using this method will only work on user defined functions, not native functions like this, Math.abs.toString()
, which would output the following.
Hopefully you can now see why function serialization isn’t the rock that high precision builds could be built upon
A Better Developer Experience
The team believes it’ll be a better Developer Experience to not support function props at all, than to support them in an unreliable and error prone way. But all is not lost!
Enter Gatsby Staff Software Engineer / Mega Legend Grayon Hicks with one solution.
React Context
Rather than attempting to pass a function through a Slice, you can “provide” the function via React’s Context API and “consume” it from within the Slice. — Sneaky ay!
Here’s some code snippets from the PR branches in the example repository.
- The
darkMode
prop is of type boolean and will either be true or false. - The
handleTheme
prop is of type function.
main (without Slice API)
The below snippet is from the main branch and show’s how you’d typically pass props “through” a Header component.
example/error-branch
This snippet is from the example/error-branch
branch and shows an almost direct translation if the Header component were a Slice. This won’t work!
feat/add-context
What will work however is, if you move the props “inside” the Header Component so the props can be consumed via React’s Context API, leaving the Slice Component with only the alias
prop.
And here’s what the “inside” of the Header Component looks like, both the darkMode
and handleTheme
props are made available via React’s Context API Consumer.
Using React Context With Gatsby
Now… this is all well and good, and you can see this working on this Gatsby Cloud PR Preview but, are you now wondering, how to use React’s Context API with Gatsby?
I got ya back, Jack. I’ve written a post on my blog and aptly named it: How to use React’s Context API with Gatsby — (didn’t see that plot twist coming, did ya!)
I’m sure there’s a few other ways to work round passing functions into Slice Components, most, I imagine will require a little bit of a refactor, but the Slice API can really help speed up builds when site-wide code or content changes occur in common components, and I for one totally think it’s worth the effort.
Lemme know if you have any q’s, as always, I’m on Twitter: @PaulieScanlon