I tried React Context, Redux and Zustand stores in the same app
Tags: redux, zustand, react
Published: 2022-11-17 18:34:42

Me and my team are very used to working with redux since we usually work with big scalable apps that require a complex state management tool — where redux comes super handy.

However, one day we had to create a simple web page to display some specific information about a product that we were working on. This app would be a quite simple app that _might_ get eventually bigger, therefore it might not require a complex state management option.

That put us in the situation where we wanted to think in ˆ management options, and not only Redux. Which works perfectly, but hey, what a great chance to learn a bit more of other alternatives!

We thought of three options for this exercise:

- [React Context](https://reactjs.org/docs/context.html) via the `useContext` hook
- [Zustand](https://github.com/pmndrs/zustand)
- Redux via [ReduxToolkit](https://github.com/reduxjs/redux-toolkit)

Let’s create a brand new CRA and add these three options so we can see how different the setup would be.

_For the sake of simplicity all these stores will be a contrived example with a counter, increment and decrement actions_. When the app gets bigger, _good organization practices_ should be considered for correct scalability.

The example is based on a create-react-app project.

We’ll start with withe **React Context** via `useContext`, since it’s a hook that comes already with React and should be simpler to understand if you have not used either of the other two incoming alternatives.

First, create a _store_

```tsx
// scr/stores/context/store.js

import React, { useState } from "react";

export const CounterContext = React.createContext();

export const CounterContextProvider = ({ children }) => {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  const decrement = () => {
    setCount(count - 1);
  };

  return (
    <CounterContext.Provider value={{ count, increment, decrement }}>
      {children}
    </CounterContext.Provider>
  );
};
```

As you can see, we used `useState` to store the state with the `counter` . For a more advanced —and close to how redux works— state management there's `useReducer` (more info).

Then, create a `Context` component that will display and change the `counter` value

```tsx

// src/components/Context.js

import { useContext } from "react";
import { CounterContext } from "../stores/context/store";

export const Context = () => {
  const { count, increment, decrement } = useContext(CounterContext);

  return (
    <div className="context">
      <h1>Context Container</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <p>Counter: {count}</p>
    </div>
  );
};
```

We access to the `value` prop elements from the store via `useContext(CounterContext)`

Finally, add it to `App.js`

```tsx

// src/App.js

import { CounterContextProvider } from "./stores/context/store";
import { Context } from "./components/Context";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <CounterContextProvider>
          <Context />
        </CounterContextProvider>
      </header>
    </div>
  );
}
```

Note that the `CounterContextProvider` _must_ wrap the `Context` component in order to make the children have access to the values from it.

And we’ve got it working. Easy. Kind of.


![context](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vzzi65oog4s87bk0c3da.png)

Now it comes **Zustand**. For a lot of people it is a _halfway_ between the simplicity of useContext and the apparent complexity of Redux.

The zustand `store`

```ts
// src/stores/zustand/store.js

import create from "zustand";

export const useCounterStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));
```

Have you noticed how this one was _remarkably_ simpler than the `useContext` wrapper function?

Now the component `Zustand`

```tsx
// src/components/Zustand.js

import { useCounterStore } from "../stores/zustand/store";

export const Zustand = () => {
  const counter = useCounterStore((state) => state.count);
  const increaseCount = useCounterStore((state) => state.increment);
  const decreaseCount = useCounterStore((state) => state.decrement);

  return (
    <div className="zustand">
      <h1>Zustand Container</h1>
      <button onClick={increaseCount}>Increment</button>
      <button onClick={decreaseCount}>Decrement</button>
      <p>Counter: {counter}</p>
    </div>
  );
};
```

Here the different values/methods should be declared one by one via with the useCounterStore .

And finally, we added the Zustand component in `App.js` . We don’t need to wrap **anything**. The component doesn't require any `Provider` as Context or Redux would, that's cool.

```tsx
// src/App.js

import { Zustand } from './components/Zustand';
import { CounterContextProvider } from './stores/context/store';
import { Context } from './components/Context';

function App() {
  return (
    <div className='App'>
      <header className='App-header'>
        // no wrapper needed!
        <Zustand />
        <CounterContextProvider>
          <Context />
        </CounterContextProvider>
      </header>
    </div>
  );
}

export default App;
```

We now have _two_ stores available that behave exactly the same way

![zustand-result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/toiohyyqls7cbw2qjr2l.png)

And finally comes **Redux**, via ReduxToolKit.

First step would be creating the _store_

```tsx
// src/stores/redux-tool-kit/slices/counter.js

import { createSlice } from "@reduxjs/toolkit";

const counterSlice = createSlice({
  name: "counter",
  initialState: {
    value: 0,
  },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

// src/stores/redux-tool-kit/store.js

import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./slices/counter";

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});
```

This time the store requires two steps, the configuration via the `configureStore` method and the slice via the `createSlice` method that then goes into the reducer object from the store.

the `ReduxToolkit` component looks like this:

```tsx
// src/components/ReduxToolKit.js
import { useDispatch, useSelector } from "react-redux";
import { decrement, increment } from "../stores/redux-tool-kit/slices/counter";

export const ReduxToolKit = () => {
  const dispatch = useDispatch();
  const counter = useSelector((state) => state.counter.value);
  const increase = () => dispatch(increment());
  const decrease = () => dispatch(decrement());

  return (
    <div className="redux-toolkit">
      <h1>Redux Tool Kit Container</h1>
      <button onClick={increase}>Increment</button>
      <button onClick={decrease}>Decrement</button>
      <p>Counter: {counter}</p>
    </div>
  );
};
```

Note how we had to declare a `dispatch` method from `useDispatch` that takes as argument the actual action methods. Besides that, it looks quite similar to the Zustand functional component.

And finally, we have to wrap the `ReduxToolKit` component in the `App` component with the `Provider` component from `react-redux` and pass it to the `store` prop. Similar to what we did with the Context store passing the `value` prop.

And if we check it in the UI, it behaves exactly as the other components

![redux-1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zlsc4yu0zxsprnmy9ikn.png)

**Extra: Bundle size**
Ok, now, what about the size of each of these three options? How would it affect our bundle size in production? Let’s check the three scenarios:

**Context** is obviously 0, it comes with React, so it doesn’t affect our bundle size.

**Zustand**. This is what [bundlephobia](https://bundlephobia.com/) shows us about it.

![zustand-1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fueo80kd4o8w4bx7p7lv.png)

Only 3kb minified. That’s lightweight!

ReduxToolkit: For this to work we need `react-redux` and `@redux/toolkit`.

![phobia-1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ra3wwc6ljcpjviq0fs9y.png)

![phobia-2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fqji5aepikzczynlydjt.png)

This one is a bit heavier ~55kb minified, but nothing that should worry us.

If you want to understand a bit more about the cost of the packages weight, [this is a nice article](https://medium.com/voodoo-engineering/the-real-cost-of-javascript-dependencies-and-the-state-of-js-package-quality-a8dacd74c0ec).

**Our decision**
For the first mentioned app where we only required a simple state management system _we went with Zustand_. Only because it’s super straightforward and we didn’t want to over complicate something that didn’t require it. But either of them would have been a perfectly fine option. Also, it helped me to understand a bit better Zustand which was completely new to me.

**Conclusion**
I personally consider that RTK made it super easy to better understand the redux-pattern, and reduced a lot of boilerplate for the initial setup. Any middle-senior developer should be able to use it without much problems. Besides, the advanced use cases with thunks, middleware, immutability with immer, Redux DevTools (these are awesome for debugging) are life savers. Once you are familiar with its use you will find it hard to go back to other ‘simpler’ alternatives.

Depending on the characteristics of a project — complexity, size, architecture — any of these options are very viable. Even Context, which — honestly — I still don’t enjoy using.

Check [the code](https://github.com/esponges/react-store-alternatives) with the code if you want more specifics for the code of this article.

Initially posted in [Medium](https://medium.com/@esponges/i-tried-react-context-redux-and-zustand-stores-in-the-same-app-ce6b6477630a)