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! 🎉