DEV Community

Cover image for Use Hooks + Context, not React + Redux
Shivam
Shivam

Posted on

Use Hooks + Context, not React + Redux

Redux introduces a lot of complexity to our codebase with the excessive amount of code it requires. This makes it an imperfect solution for state management in React applications.
I will introduce the React Context API for state management and show you what makes React Hooks plus the Context API a better solution than Redux.

The React Context API
Context provides a way to pass data through the component tree without having to pass props down manually at every level.

The React Context API is React’s way of managing state in multiple components that are not directly connected.

To create a context, we’ll use the createContext method from React, which accepts a parameter for its default value:

import React from 'react';
const newContext = React.createContext({ color: 'black' });
Enter fullscreen mode Exit fullscreen mode

The createContext method returns an object with a Provider and a Consumer component:

const { Provider, Consumer } = newContext;
Enter fullscreen mode Exit fullscreen mode

The Provider component is what makes the state available to all child components, no matter how deeply nested they are within the component hierarchy. The Provider component receives a value prop. This is where we’ll pass our current value:

<Provider value={color: 'blue'}>
  {children}
</Provider>
Enter fullscreen mode Exit fullscreen mode

The Consumer, as its name implies, consumes the data from the Provider without any need for prop drilling(threading):

<Consumer>
  {value => <span>{value}</span>}}
</Consumer>
Enter fullscreen mode Exit fullscreen mode

Without Hooks, the Context API might not seem like much when compared to Redux, but combined with the useReducer Hook, we have a solution that finally solves the state management problem.

The useReducer Hook
he useReducer Hook came with React 16.8. Just like the reduce() method in JavaScript, the useReducer Hook receives two values as its argument — a reducer function and an initial state — and then returns a new state:

const [state, dispatch] = useReducer((state, action) => {
  const { type } = action;
  switch(action) {
    case 'action description':
      const newState = // do something with the action
      return newState;
    default:
      throw new Error()
  }
}, []);
Enter fullscreen mode Exit fullscreen mode

In the above block, we’ve defined our state and a corresponding method, dispatch, handling it. When we call the dispatch method, the useReducer() Hook will perform an action based on the type that our method receives in its action argument:

...
return (
  <button onClick={() =>
    dispatch({ type: 'action type'})}>
  </button>
)
Enter fullscreen mode Exit fullscreen mode

The useReducer Hook plus the Context API
let’s see what happens when we combine useReducer with Context API in order to get the ideal global state management solution for our application.
We’ll create our global state in a store.js file:

// store.js
import React, {createContext, useReducer} from 'react';

const initialState = {};
const store = createContext(initialState);
const { Provider } = store;

const StateProvider = ( { children } ) => {
  const [state, dispatch] = useReducer((state, action) => {
    switch(action.type) {
      case 'action description':
        const newState = // do something with the action
        return newState;
      default:
        throw new Error();
    };
  }, initialState);

  return <Provider value={{ state, dispatch }}>{children}</Provider>;
};

export { store, StateProvider }
Enter fullscreen mode Exit fullscreen mode

In our store.js file, we used the createContext() method from React that is explained earlier to create a new context.
Remember that the createContext() method returns an object with a Provider and Consumer component. This time, we’ll be using only the Provider component and then the useContext Hook when we need to access our state.
When we need to manipulate our state, we’ll call the dispatch method and pass in an object with the desired type as its argument.
In our StateProvider, we returned our Provider component with a value prop of state and dispatch from the useReducer Hook.

Accessing our state globally
In order to access our state globally, we’ll need to wrap our root <App/> component in our StoreProvider before rendering it in our ReactDOM.render() function:

// root index.js file
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { StateProvider } from './store.js';

const app = (
  <StateProvider>
    <App />
  </StateProvider>
);
ReactDOM.render(app, document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

Now, our store context can be accessed from any component in the component tree. To do this, we’ll import the useContext Hook from react and the store from our ./store.js file:

// exampleComponent.js
import React, { useContext } from 'react';
import { store } from './store.js';

const ExampleComponent = () => {
  const globalState = useContext(store);
  console.log(globalState); // this will return { color: red }
};
Enter fullscreen mode Exit fullscreen mode

Adding and removing data from our state
We’ve seen how we can access our global state.
In order to add and remove data from our state, we’ll need the dispatch method from our store context. We only need to call the dispatch method and pass in an object with type (the action description as defined in our StateProvider component) as its parameter:

// exampleComponent.js
import React, { useContext } from 'react';
import { store } from './store.js';

const ExampleComponent = () => {
  const globalState = useContext(store);
  const { dispatch } = globalState;

  dispatch({ type: 'action description' })
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

Redux works for state management in React applications and has a few advantages, but its verbosity makes it really difficult to pick up, and the ton of extra code needed to get it working in our application introduces a lot of unnecessary complexity.

On the other hand, with the useContext API and React Hooks, there is no need to install external libraries or add a bunch of files and folders in order to get our app working. This makes it a much simpler, more straightforward way to handle global state management in React applications.

Top comments (0)