Create Redux-like state management with React ⚛
Tags: react, tutorial, typescript
Published: 2021-12-25 10:24:51

## Introduction

[Redux](https://redux.js.org/) is probably the most popular state management library in React environment. At the time I'm writing this article, it has nearly 6.3 million weekly downloads on [npm](https://www.npmjs.com/package/redux), but despite the fact that it's so popular it doesn't mean that it's a must-have in every project.

In this article, I would like to show you how to create a Redux-like approach to state management using only React built-in utilities.

Before we begin, I would like to note that this article is for educational purposes only and if you're about to start working on a commercial application that contains a lot of complex business logic, it would be better to use Redux or some other state management library e.g. [MobX](https://mobx.js.org/README.html), just to avoid additional overhead and refactoring in the future.

## Code

To keep it as simple as possible, let's create some basic counter app that has two options - incrementing and decrementing counter value. We will start from declaring initial state and types for our actions.

```ts
type State = { counter: number };

type Action = { type: "INCREMENT" } | { type: "DECREMENT" };

const initialState: State = { counter: 0 };
```

Now we need to create reducer - a simple function that is responsible for modifying and returning updated state based on action type.

```ts
const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case "INCREMENT":
      return {
        ...state,
        counter: state.counter + 1
      };
    case "DECREMENT":
      return {
        ...state,
        counter: state.counter - 1
      };
    default:
      return state;
  }
};
```

Once we have our reducer ready, we can pass it to the `useReducer` hook that returns current state paired with `dispatch` method that's responsible for executing actions, but in order to use it all across our application we need some place where we can store it. For that, we will use React context.

```tsx
import {
  createContext,
  Dispatch,
  ReactNode,
  useContext,
  useReducer
} from "react";

const StoreContext = createContext<[State, Dispatch<Action>]>([
  initialState,
  () => {} // initial value for `dispatch`
]);

export const StoreProvider = ({ children }: { children: ReactNode }) => (
  <StoreContext.Provider value={useReducer(reducer, initialState)}>
    {children}
  </StoreContext.Provider>
);

export const useStore = () => useContext(StoreContext);
```

Take a look at the `useStore` hook we created using `useContext`. This hook will allow us to access `state` and `dispatch` in each child component of `StoreProvider`.

In this example, I will use `StoreProvider` in `render` method which will cause our state to be accessible globally, but I would like to note that you should keep your state as close to where it's needed as possible, since updates in context will trigger re-render in each of the providers' child components which might lead to performance issues once your application grows bigger.

```tsx
import { render } from "react-dom";
import App from "./App";
import { StoreProvider } from "./store";

const rootElement = document.getElementById("root");

render(
  <StoreProvider>
    <App />
  </StoreProvider>,
  rootElement
);
```

Now we can create a UI for our counter app and see `useStore` hook in action.

```tsx
export default function App() {
  const [state, dispatch] = useStore();

  return (
    <div className="container">
      <button onClick={() => dispatch({ type: "INCREMENT" })}>Increment</button>
      <button onClick={() => dispatch({ type: "DECREMENT" })}>Decrement</button>
      <p>Counter: {state.counter}</p>
    </div>
  );
}
```

And that's it!

## Demo

If you want to take a closer look at code and see how this application works live, check out this sandbox 👀

{% codesandbox redux-like-state-management-yl3w8 %}

Thanks for reading! 👋