The ultimate solution to global state management in React
Tags: react, context, redux, slice
Published: 2022-04-02 11:38:51

[**react-context-slices**](https://react-context-slices.github.io) offers a zero-boilerplate solution to global state management in React by seamlessly integrating both Redux and React Context.

![react-context-slices is the ultimate solution to global state management in React](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4xqzmt3eb8g7bnp85df1.png)

## Installation

```javascript
npm i react-context-slices
```

## How to use it (javascript)

react-context-slices seamlessly integrates the best of both worlds with zero-boilerplate. You define either React Context or Redux slices, and the function `getHookAndProviderFromSlices` will get you a hook, `useSlice`, and a provider.

What differentiates a slice of being a Redux slice from being a React Context slice is the presence of the key `reducers` in its definition. If the key is present, then it will be a Redux slice. Otherwise it will be a React Context slice.

```javascript
// slices.js
import getHookAndProviderFromSlices from "react-context-slices";

export const { useSlice, Provider } = getHookAndProviderFromSlices({
  slices: {
    count1: {
      // Redux slice
      initialState: 0,
      reducers: {
        increment: (state) => state + 1,
      },
    },
    count2: {
      // React Context slice
      initialArg: 0,
    },
    count3: {
      // React Context slice
      initialArg: 0,
      reducer: (state, { type }) => {
        switch (type) {
          case "increment":
            return state + 1;
          default:
            return state;
        }
      },
    },
    todos: {
      // Redux slice
      initialState: [],
      reducers: {
        add: (state, { payload }) => {
          state.push(payload);
        },
      },
    },
    // rest of slices (either Redux or React Context slices)
  },
});
```

```javascript
// app.jsx
import { useSlice } from "./slices";

const App = () => {
  const [count1, reduxDispatch, { increment }] = useSlice("count1");
  const [count2, setCount2] = useSlice("count2");
  const [count3, dispatchCount3] = useSlice("count3");
  const [todos, , { add }] = useSlice("todos");
  const [firstTodo] = useSlice("todos", (state) => state[0]);

  return (
    <>
      <div>
        <button onClick={() => reduxDispatch(increment())}>+</button>
        {count1}
      </div>
      <div>
        <button onClick={() => setCount2((c) => c + 1)}>+</button>
        {count2}
      </div>
      <div>
        <button onClick={() => dispatchCount3({ type: "increment" })}>+</button>
        {count3}
      </div>
      <div>
        <button onClick={() => reduxDispatch(add("use react-context-slices"))}>
          add
        </button>
        {todos.map((t, i) => (
          <div key={i}>{t}</div>
        ))}
      </div>
      <div>{firstTodo}</div>
    </>
  );
};

export default App;
```

The `useSlice` hook is used to fetch the slices, either the Redux slices or React Context slices. For Redux slices you can pass an optional selector as a second parameter to the function. As a first parameter, you always pass the name of the slice.

For React Context slices it returns a tuple where the first element is the value of the state for the slice and the second element is either a setter or dispatch function, depending on if you defined a reducer for the slice (key `reducer`, not `reducers`).

For Redux slices, the hook returns an array where the first element is the state value selected, the second is a dispatch function, and the third is an object containing the actions for the slice.

## Other features

react-context-slices allows us to retrieve the initial state of a React Context slice from local storage (in the case of the web) or from async storage (in the case of React Native).

```javascript
// slices.js
import getHookAndProviderFromSlices from "react-context-slices";

export const {useSlice, Provider} = getHookAndProviderFromSlices({
  slices: {
    count: {initialArg: 0, isGetInitialStateFromStorage: true}, // React Context slice
    // rest of slices
  }
});
```

Then we need to persist the state value of the slice every time it changes.

It also allows the definition of **middleware** for a given React Context slice to intercept and customise actions workflow. This way, we can make API calls, logging, or other side effects. Middleware does not have access to state value (in the case of React Context slices).

```javascript
// slices.js
import getHookAndProviderFromSlices from "react-context-slices";

export const {useSlice, Provider} = getHookAndProviderFromSlices({
  slices:{
    todos: { // React Context slice
      initialArg: [],
      reducer: (state, action) => {
        // ...
      },
      middleware: [
        () => next => action => { // first middleware applied
          console.log('dispatching action:', action);
          next(action);
        },
        (dispatch) => next => action => { // second middleware applied
          if(typeof action === 'function'){
            return action(dispatch);
          }
          next(action);
        },
      ],
    },
    // rest of slices
  }
});
```

## Typescript

react-context-slices has also support for typescript.

```javascript
// slices.ts
import getHookAndProviderFromSlices, {
  defineSlice,
} from "react-context-slices";

export const { useSlice, Provider } = getHookAndProviderFromSlices({
  slices: {
    count1: defineSlice<number>({
      // Redux slice
      initialState: 0,
      reducers: {
        increment: (state) => state + 1,
      },
    }),
    count2: defineSlice<number>({
      // React Context slice
      initialArg: 0,
    }),
    count3: defineSlice<number>({
      // React Context slice
      initialArg: 0,
      reducer: (state, { type }) => {
        switch (type) {
          case "increment":
            return state + 1;
          default:
            return state;
        }
      },
    }),
    todos: defineSlice<string[]>({
      // Redux slice
      initialState: [],
      reducers: {
        add: (state, { payload }) => {
          state.push(payload);
        },
      },
    }),
    // rest of slices (either Redux or React Context slices)
  },
});
```

```javascript
// app.tsx
import { useSlice } from "./slices";

const App = () => {
  const [count1, reduxDispatch, { increment }] = useSlice<number>("count1");
  const [count2, setCount2] = useSlice<number>("count2");
  const [count3, dispatchCount3] = useSlice<number>("count3");
  const [todos, , { add }] = useSlice<string[]>("todos");
  const [firstTodo] = useSlice<string[], string>("todos", (state) => state[0]);

  return (
    <>
      <div>
        <button onClick={() => reduxDispatch(increment())}>+</button>
        {count1}
      </div>
      <div>
        <button onClick={() => setCount2((c) => c + 1)}>+</button>
        {count2}
      </div>
      <div>
        <button onClick={() => dispatchCount3({ type: "increment" })}>+</button>
        {count3}
      </div>
      <div>
        <button onClick={() => reduxDispatch(add("use react-context-slices"))}>
          add
        </button>
        {todos.map((t, i) => (
          <div key={i}>{t}</div>
        ))}
      </div>
      <div>{firstTodo}</div>
    </>
  );
};

export default App;
```

## Summary

react-context-slices seamlessly integrates the best of both worlds, Redux and React Context, in a zero-boilerplate solution.

Use [**react-context-slices**](https://react-context-slices.github.io) for global state management in React and React Native.