Install simpler alternative to Redux for React
Redux is a very powerful library, which is particularly used in complex web applications.
However, setting up a Redux store including actions and reducers can be quite challenging and it raises the question of whether all this is actually necessary for own applications.
In times when class components are increasingly being replaced by functional components, more alternatives emerge.
One such alternative is zustand (the German word for state), which manages stores using its own hooks.
Setup project
First use create-react-app script to setup a test project:
npx create-react-app test-app --template typescript
Switch to new test-app
subfolder and run
npm i zustand
to install all required dependencies.
Create store
Setting up a new store is very easy and it is done through the factory function create
.
To do this, you have to place the file useCounter.ts
in src/hooks
subdirectory and start with the following code:
import { create } from 'zustand';
import { persist as persistStore, createJSONStorage } from 'zustand/middleware';
// define the interface for the store
export interface ICounterStore {
// properties with values
counter: number;
foo: string;
// all methods will be handled as actions
decrease: () => void;
increase: () => void;
reset: () => void;
updateFoo(newFoo: string): void;
}
// with `create` we build a new hook
// which provides us an up-to-date version
// of an `ICounterStore` object
export const useCounter = create(
// `persistStore` creates an "initializer" which keeps
// sure that we setup an initial `ICounterStore` which
// can later be persist in `localStorage` or `sessionStorage` or
// any other compatible store
persistStore<ICounterStore>(
// this event callback is executed, when the store is initialized
//
// `updateState()` is the function that has to be used
// to update the store. It is not necessary to update all properties
// in an action at once as you can see in the example below.
//
// To get the current `ICounterStore` object, you have to use the
// `getState()` function.
(updateState, getState) => {
const initialState: ICounterStore = {
// initial props
counter: 0,
foo: 'bar',
// actions
decrease: () => {
updateState({ counter: getState().counter - 1 });
},
increase: () => {
updateState({ counter: getState().counter + 1 });
},
reset: () => {
updateState({ counter: 0 });
},
updateFoo: (newFoo: string) => {
updateState({ foo: newFoo });
},
};
return initialState;
},
// settings to persist the updated `ICounterStore` in
// browser as `my-app-counter` inside
// `sessionStorage` as JSON string
//
// if you want to store the data even when the browser
// closes you should use `localStorage` instead
{
name: 'my-app-counter',
storage: createJSONStorage(() => sessionStorage),
}
)
);
That’s all!
What we now have is a useCounter()
hook that we can now use in all our functional components.
With this, we can separate stores’ functionality and lifespan cleanly from each other depending on the use case.
Use store in UI
The rest is quite simple.
Open src/App.tsx
and replace its content with the following test code:
import React from 'react';
import { useCounter } from './hooks/useCounter';
function App() {
const counter = useCounter();
return (
<div>
<div>Current value: {counter.value}</div>
<button onClick={() => counter.increase()}>Increase</button>
<button onClick={() => counter.reset()}>Reset</button>
<button onClick={() => counter.decrease()}>Decrease</button>
</div>
);
}
export default App;
Run
npm start
to start the dev environment on http://localhost:3000
.
If you reload the tab, to test if persistence works or not, keep in mind: if you close the tab or the browser, the state will be reinitialized, because we are using sessionStorage in this example!
Conclusion
You don’t have to think it always has to be like Redux and almost 43,000 ⭐⭐⭐⭐⭐ on GitHub speaking for themselves 😉
And if you like it, you can support the project as well.
Have fun trying it out! 🎉