Seamlessly integrate Zustand in Gatsby & React for lightweight state management. Optimize performance with an SEO-friendly guide for lightning-fast web development.
Why it's worth to use Zustand?
If you want to know the all course concepts, technologies and understand what kind of Node and npm versions you need, check the initial lesson - Course overview.
If you already read this article - jump into Gatsby5 | Tailwind | TypeScript | Cypress | jest and RTL template and check commits to understand the progress.
In previous lesson - 🌟 Organized Git commits with Commitlint, we configured Commitlint and we explored commits conventions. Now it's time to add Zustand for easy and lightweight state management.
Why do I want to use Zustand? To understand all reasons you should consider reading 🥇 Comparing Redux with Zustand for state management in React article, in which I'm comparing Zustand with Redux. However, if you don't have time I need to add here my reasons quickly:
- it's super lightweight,
- lack of boilerplate,
- runtime fast and modular,
- observable based,
- well documented.
So, with this knowledge, it's time to configure Zustand for our Gatsby template project.
We need to start with a simple installation command in the terminal: npm i --save zustand. The new package will be added to our package.json file.
Querying page metadata with GraphQL
Let's say we want to query our home page metadata and assign it to Zustand store. The store doesn't exist yet, but we'll create it soon. We want to achieve it to avoid prop-drilling across many components that will be used on our page. So, firstly we need to create some GraphQL query in home page.
It's typical Gatsby stuff. We need some data from the file system or other sources and we're able to query it. In this case, we're getting information from gatsby.config.ts file i which we specify our site title and site URL in the following object.
Creating separate component for testing
To test our Zustand integration we need a component that will consume the store. Let's call it HomeView and let's add it inside the views/home/home.view.tsx file.
Now in our HomePage component which is pages/index.tsx file, we need to use our HomeView.
The title is now hardcoded but in the future, it will be taken from Zustand store. Before we continue, we need one more important stuff. We're using Tailwind in our project and we added the new directory in which we'll add our views components, so we need to update tailwind.config.js file.
If you are interested in why it's worthy to add separated components that group the view, see the following ⭐ Porting for React applications article.
Creating Zustand store and using it
Now it's time to create a Zustand store and attach it to our HomeView component. So, let's start with store and types definitions. Let's add the file store/home/home.store.ts.
We've added some types definitions for our state. It will have 2 possible shapes. The idle one which reflects nothing happened yet. It's just a temporary state to indicate the state between the server/client not happened yet.
The ready state reflects the situation in which we've data from server and data is synced with server/client.
Next, we created the store via create function and we passed type to this function to be protected by TypeScript. In addition, we created an initial state which is equal to is: "idle" object.
At the end, we added a custom selector hook that throws an error if we try to perform a read operation on the store when the state is idle. Why do we use this weird technique? The technique is called exhaustiveness checking and it will protect us from allocating not needed properties in objects and will guide us by hand when we try to access these properties.
But why we're throwing an exception? Imagine a situation when you will use a page, query the data with GraphQL, and want to assign this data to Zustand store to be able to consume it in nested components. If you forget to do a sync (this we still need to implement), there is a risk that data may not exist yet. To be able to find this problem faster we're throwing an exception.
Ok, we have a store, so now we need to use it in our HomeView component.
Syncing the state with a custom hook
There is still one missing important part - sync between the client and server state injected when we run the build process in Gatsby. Unfortunately, we need to write this logic on our own. The custom hook will be perfect for this case. Let's call it useStoreSync and add it to development-kit/use-store-sync.ts file.
To sync the state between server/client we need to:
- pass the default state (taken from GraphQL query),
- set this state as default one,
- check if it is a server or not,
- if it is a server - we need to create a store once and return it,
- if it's a client we need to set state as the passed one.
Our hook now may be used in HomePage component - the pages/index.tsx file, in the following way:
We've passed here the useHomeStore instance and the initial state to set. Now our hook will do whole meat and the state will be the same during build process for nested components and after rehydrating on the client side!
Interested about more details and how to do it in NextJS? Feel free to dive through 🌟 How to integrate state management in Zustand with NextJS article.
Our Zustand is attached to Gatsby ecosystem! Now when you inspect the dev tools you'll see that initially generated HTML during build process is the same as the one created by the client later.
The final result
To get current result jump into Gatsby5 | Tailwind | TypeScript | Cypress | jest and RTL template and check commits to understand the progress.
Conclusions and next steps
Now, we are able to avoid prop drilling with a simple utility hook call at the beginning of the page. The best part is that this hook will work also in NextJS for server components and statically generated/server-side rendered pages.
If you want to check how just go to 🌟 How to integrate state management in Zustand with NextJS article.
In the next lesson, we'll configure page generator and view templates.
- 1. Introduction
- 2. Creating project