Lightweight Alternatives to Redux
Tags: redux, react, javascript, webdev
Published: 2022-01-28 03:55:54

_by author [Aman Mittal](https://blog.openreplay.com/authors/aman-mittal)_


When building a React application, there are many options to choose from when it comes to a state management library. Redux has been the go-to choice for many development teams over the years. However, React's ever evolving ecosystem and unopinionated nature provide various solutions where Redux might not be the best choice.

In this article, let's look at these lightweight alternatives to Redux that you can use in your React applications to manage the application state.

## React Hooks

Depending on the React application, it might not be a state management library. Hooks available in React brought the state to functional components. It's a powerful feature that allows a function component to manage its own local state. Not all applications would require having everything in a central state. It might be over-engineered for such cases.

You can create a local state management system for the functional component using React hooks such as `useState`, `useEffect`, ' useReducer'. These hooks are available in React out of the box. You can create custom hooks and separate them from the presentational components to make the code more readable.

## React Context and useReducer hook

There are two types of states to deal with in React apps. The first type is the local state that is used only within a React component. The second type is the global state that can be shared among multiple components within a React application.

Another API that React ships out of the box is Context. It falls under the second type of shared state category. Using it with the `useReducer` hook, you can implement a custom global lightweight state management system. The `useReducer` hook is a great way to manage complex state objects and state transitions. It is different from `useState`. It updates the state by accepting a reducer function and an initial state. Then, it returns the actual state and a dispatch function. This dispatch function is used to make changes to the state.

If you have used Redux in the past, that may sound familiar. Conceptually, Redux uses the same concept of dispatching a function to make changes.

With an example, let's see you can use them together. Start by importing the required hooks from React library.

```javascript
import { useReducer, createContext } from 'react';
```

Next, create an empty context and an initial state object. If there is no initial state, you can leave this object empty. For example, in the code snippet below, we've defined an initial state with a `todos` array with a single item.

```javascript
export const TodosContext = createContext();

const initialState = {
  todos: [
    {
      id: 0,
      item: 'Buy a notebook'
    }
  ]
};
```

The next step is to define a function called `reducer`. It is going to take two arguments, the current state, and action. This function is used to update the state object whenever there is an action dispatched. The action dispatches when the app user performs an activity with it. One example of an action is a user adding more items to the `todos` array.

```javascript
const reducer = (state, action) => {
  switch (action.type) {
    case 'ADD_ITEM':
      return {
        todos: [...state.todos, action.payload]
      };
    default:
      return {
        state
      };
  }
};
```

Next, define a TodoContextProvider that will be the central store.

```javascript
export const TodosContextProvider = props => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <TodosContext.Provider value={[state, dispatch]}>
      {props.children}
    </TodosContext.Provider>
  );
};
```

The `useReducer` hook creates a reducer using the reducer function defined in the previous code snippet. This reducer is the first argument to the hook. The initialState is passed as the second argument.

After that, you will have to wrap the set of components or the initial App component of the React application with the `TodosContextProvider`. This way, the Context API makes the state and dispatch function available anywhere without passing anything down the component tree.

```javascript
export const App = () => {
  return (
    <TodosContextProvider>
      <MainApp />
    </TodosContextProvider>
  );
};
```

Using the `useContext` hook from React within the `MainApp` function component, you can access values from the `TodosContext`. In the same component, you can dispatch the action to add more `todos`.

```javascript
import { useContext, useState } from 'react';

export const MainApp = () => {
  const [state, dispatch] = useContext(TodosContext);

  // the below is a local state variable to get the item name
  const [item, setItem] = useState('');

  const addTodos = event => {
    if (name !== '') {
      dispatch({
        type: 'ADD_EXPENSE',
        payload: { id: uuid(), item }
      });

      // clean input fields
      setItem('');
    }
  };

  // ... rest of the component code
};
```

## Zustand

[Zustand](https://github.com/pmndrs/zustand) is an open-source state management library that uses the same flux principles but in a simplified manner. It is one of the lightest management libraries available with bundle size at 2kB according to [bundlephobia](https://bundlephobia.com/package/zustand@3.6.9) when minified.

It tackles a few of the common pitfalls like:

- [Zombie children](https://react-redux.js.org/api/hooks#stale-props-and-zombie-children)
- [React Concurrency](https://github.com/bvaughn/rfcs/blob/useMutableSource/text/0000-use-mutable-source.md)
- [Context loss](https://github.com/facebook/react/issues/13332)

It is straightforward to get started with Zustand when it comes to creating a store using the hook `create` method provided by the library. It accepts a callback function as its first argument. The store created is a React hook.

```javascript
import create from 'zustand';

const useStore = create(set => ({
  todos: [
    {
      id: 1,
      item: 'Buy a notebook'
    },
    {
      id: 2,
      item: 'Buy coffee beans'
    }
  ],
  addTodo: todo =>
    set(state => ({
      todos: [{ item: pokemon.item, id: uuid() }, ...state.todos]
    }))
}));
```

The callback function accepts a `set` function as its argument that is used when manipulating the state.

The `create` method returns the `useStore` as a hook such that you can use it in any of the React components.

```javascript
const App = () => {
  const todos = useStore(state => state.todos);

  // ...
};
```

Zustand also recommends a practice of memoizing selectors with a `useCallback` hook from React. This prevents unnecessary computations on each render. This way, the React application is optimized for performance in concurrent mode.

Due to its unopinionated nature, Zustand is a great lightweight solution for state management in a React application. It has less boilerplate code and doesn't wrap the React app in context providers. You can create multiple stores to handle different states and actions. It is also compatible with well-known tools like Redux Devtools and offers built-in middleware such as `persist` to persist the state of the application.

## Open Source Session Replay
Debugging a web application in production may be challenging and time-consuming. [OpenReplay](https://github.com/openreplay/openreplay) is an Open-source alternative to FullStory, LogRocket and Hotjar. It allows you to monitor and replay everything your users do and shows how your app behaves for every issue.
It’s like having your browser’s inspector open while looking over your user’s shoulder.
OpenReplay is the only open-source alternative currently available.

![OpenReplay](https://raw.githubusercontent.com/openreplay/openreplay/main/static/replayer.png)

Happy debugging, for modern frontend teams - [Start monitoring your web app for free](https://github.com/openreplay/openreplay).



## Jotai

[Jotai](https://github.com/pmndrs/jotai) is another open-source state management library developed and maintained by the same team behind Zustand. While the latter is based on flux principles, the former is based on the atomic approach. The state in an application, when split into atoms, is much smaller and lighter when compared to a redux store.

According to [bundlephobia](https://bundlephobia.com/package/jotai@1.5.3), the bundle size at 8.1kB when minified.

For all purposes, the Jotai has a simple API that revolves around `Provider`, `atom`, and `useAtom` hook. An atom is stored inside the React state tree. It uses React Context, so you will have to take care of optimizing the re-rendering of components.

Here is an example of creating a primitive atom with initial values:

```javascript
import { atom } from 'jotai';

const countAtom = atom(0);
const countryAtom = atom('Japan');
const citiesAtom = atom(['Tokyo', 'Kyoto', 'Osaka']);
```

Using the atom in a React component is equivalent to using `React.useState`.

```javascript
import { useAtom } from 'jotai';

function Counter() {
  const [count, setCount] = useAtom(countAtom);
  return (
    <h1>
      {count}
      <button onClick={() => setCount(c => c + 1)}>one up</button>
    </h1>
  );
}
```

Jotai offers a hook called [useAtomDevTools](https://jotai.org/docs/api/devtools) that can be used to manage Redux DevTools integration for a particular atom.

## Valtio

[Valtio](https://github.com/pmndrs/valtio) is a lightweight proxy based state management system for React applications. Proxy is a design pattern for observing an object and modifying it, with each object can have its own custom behavior.

Creating a state with Valtio means you have to wrap the `state` object in the React app with `proxy`. Any updates to the `state` object are handled by the library.

```javascript
import { proxy } from 'valtio';

const state = proxy({
  count: 0
});
```

Since the library handles tracking of the updates to the state object, you can mutate it directly in a React component, just like a normal JavaScript object.

```javascript
setInterval(() => {
  ++state.count;
}, 1000);
```

Valtio has a built-in hook called `useProxy` that reads a value from snapshots. Using it, the React component will only re-render when the part of the state in the component changes.

```javascript
const Counter = () => {
  const snapshot = useProxy(state);
  return (
    <div>
      {snapshot.count}
      <button onClick={() => ++state.count}>Add</button>
    </div>
  );
};
```

According to [bundlephobia](https://bundlephobia.com/package/valtio@1.2.10), the bundle size at 6.7kB when minified.

## Conclusion

Picking up a state management library that fulfills the need of having a centralized store in your React application comes with a variety of options. We went through some of those options in this post. Some of these options are powerful, and while the vanilla React hooks way of dedicating a component's state and not creating a centralized store for everything is a tried and tested approach.