State management using only React Hooks
Tags: react, redux, javascript, webdev
Published: 2019-09-16 14:54:12

---
title: State management using only React Hooks
published: true
tags: react,redux,javascript,webdev 
canonical_url: https://blog.logrocket.com/state-management-using-only-react-hooks/
cover_image: https://thepracticaldev.s3.amazonaws.com/i/3spjpfemkdhjn8y9bmtz.jpeg
---
**Written by [Ovie Okeh](https://blog.logrocket.com/author/ovieokeh/)**✏️

The Hooks API has brought with it a whole new way of writing and thinking about React apps. One of my favorite Hooks so far is `useReducer`, which allows you to handle complex state updates, and that’s what we’ll be looking at in this article.

Managing shared state in larger React apps usually involved pulling in third-party libraries like Redux and MobX. These third-party libraries made it easier to update your application’s state in a more predictable and fine-grained way, but they usually came with extra overhead and learning curves.

The good news is that you can now reap the same benefits without the extra packages and learning curve — OK, maybe a tiny curve — thanks to `useReducer`. By the end of this article, you should be able to manage your application’s state in a predictable manner without any third-party package.

> **Note:** This is not an article bashing Redux, MobX, or any other state management package. They all have cases where they shine. This article simply aims to familiarize you with `useReducer` and how you can use it as an alternative to manage an application’s state.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## What is `useReducer`?

Before we get into how to use `useReducer` to manage shared state, we’ll have to deconstruct it so we can understand it better.

It’s one of the new custom Hooks that now ship with [React since v16.8](https://reactjs.org/docs/hooks-intro.html). It allows you to update parts of your component’s state when certain actions are dispatched, and it is very similar to how Redux works.

It takes in a reducer function and an initial state as arguments and then provides you with a state variable and a dispatch function to enable you to update the state. If you’re familiar with how Redux updates the store through reducers and actions, then you already know how `useReducer` works.

## How does `useReducer` work?

A `useReducer` requires two things to work: an initial state and a reducer function. We’ll see how they look below and then explain in detail what each of them is used for.

Consider the following snippet of code:

```jsx
// we have to define the initial state of the component's state
const initialState = { count: 0 }

// this function will determine how the state is updated
function reducer(state, action) {
  switch(action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 }
    case 'DECREMENT':
      return { count: state.count - 1 }
    case 'REPLACE':
      return { count: action.newCount }
    case 'RESET':
      return { count: 0 }
    default:
      return state
  }
}

// inside your component, initialize your state like so
const [state, dispatch] = useReducer(reducer, initialState);
```

In the code snippet above, we have defined an initial state for our component — a reducer function that updates that state depending on the action dispatched — and we initialized the state for our component on line 21.

For those of you who have never worked with Redux, let’s break down everything.

### The `initialState` variable

This is the default value of our component’s state when it gets mounted for the first time.

### The reducer function

We want to update our component’s state when certain actions occur. This function takes care of specifying what the state should contain depending on an action. It returns an object, which is then used to replace the state.

It takes in two arguments: `state` and `action`.

`state` is your application’s current state, and `action` is an object that contains details of the action currently happening. It usually contains a `type:` that denotes what the action is. `action` can also contain more data, which is usually the new value to be updated in the state.

An action may look like this:

```jsx
const replaceAction = {
  type: 'REPLACE',
  newCount: 42,
}
```

Looking back at our reducer function, we can see a switch statement checking the value of `action.type`. If we had passed `replaceAction` as the current action to our reducer, the reducer would return an object `{ count: 42 }`, which would then be used to replace the component’s state.

### Dispatching an action

We know what a reducer is now and how it determines the next state for your component through actions being dispatched. How, though, do we dispatch such an action?

Go back to the code snippet and check line 21. You’ll notice that `useReducer` returns two values in an array. The first one is the state object, and the second one is a function called `dispatch`. This is what we use to dispatch an action.

For instance, if we wanted to dispatch `replaceAction` defined above, we’d do this:

```jsx
dispatch(replaceAction)

// or

dispatch({
  type: 'REPLACE',
  newCount: 42,
})
```

Dispatch is nothing more than a function, and since functions in JavaScript are first-class citizens, we can pass them around to other components through props. This simple fact is the reason why you can use `useReducer` to replace Redux in your application.

## Replacing Redux with `useReducer`

Now for the reason you’re actually reading this article. How do you use all these to get rid of Redux?

Well, we know how to dispatch an action to update a component’s state, and now we’re going to look at a scenario where the root component’s state will act as the replacement for the Redux store.

Let’s define the initial state of our store:

```jsx
const initialState = {
  user: null,
  permissions: [],
  isAuthenticating: false,
  authError: null,
}
```

Now our reducer function:

```jsx
function reducer(state, action) {
  switch(action.type) {
    case 'AUTH_BEGIN':
      return {
        ...state,
        isAuthenticating: true,
      }

    case 'AUTH_SUCCESS':
      return {
        isAuthenticating: false,
        user: action.user,
        permissions: action.permissions
        authError: null,
      }

    case 'AUTH_FAILURE':
      return {
        isAuthenticating: false,
        user: null,
        permissions: []
        authError: action.error,
      }

    default:
      return state
  }
}
```

And, finally, our root component. This is going to hold the store and pass the required data and the dispatch function down to the components that need them. This will allow the children components to read from and update the store as required.

Let’s see how it looks in code:

```jsx
function App() {
  const [store, dispatch] = useReducer(initialState)

  return (
    <React.Fragment>
      <Navbar user={store.user} />
      <LoginPage store={store} dispatch={dispatch} />
      <Dashboard user={store.user} />
      <SettingsPage permissions={store.permissions} />
    </React.Fragment>
  )
}
```

We have `App` set up to handle the store, and this is where we pass the store values down to the children components. If we were using Redux, we’d have had to use `Provider` to wrap all the components, create a separate store, and then for each component that needs to connect to the store, wrap them in a HOC with `connect`.

With this approach, however, we can bypass using all that boilerplate and just pass in the store values directly to the components as props. We could have as many stores, reducers, initialStates, etc. as is required without having to bring in Redux.

OK, let’s write a login function, call it from the `<LoginPage />` component, and watch how the store gets updated.

```jsx
async function loginRequest(userDetails, dispatch) {
  try {
    dispatch({ type: 'AUTH_BEGIN' })
    const { data: { user, permissions } } = await axios.post(url, userDetails)
    dispatch({ type: 'AUTH_SUCCESS', user, permissions }) 
  } catch(error) {
    dispatch({ type: 'AUTH_FAILURE', error: error.response.data.message })
  }
}
```

And we’d use it like this in the `LoginPage` component:

```jsx
function LoginPage(props) {
  // ...omitted for brevity
  const handleFormSubmit = async (event) => {
    event.preventDefault()

    await loginRequest(userDetails, props.dispatch)
    const { authError } = props.store
    authError
      ? handleErrors(authError)
      : handleSuccess()
  }
  // ...omitted for brevity
}
```

We’ve now been able to update a store variable that is being read from several other components. These components get the new value of `user` and `permissions` as soon as the the reducer returns the new state determined by the action.

This is a very modular way to share dynamic data between different components while still keeping the code relatively simple and free from boilerplate. You could improve on this further by using the [`useContext` Hook](https://reactjs.org/docs/hooks-reference.html#usecontext) to make the store and dispatch function available to all components without having to manually pass it down by hand.

## Caveats

There are some rather important limitations to `useReducer` that we need to talk about if we’re being objective. These limitations are what may hinder you from managing all your application’s state with `useReducer`.

### Store limitations

Your store is not truly global. Redux’s implementation of a global store means that the store itself isn’t tied to any component; it’s separate from your app.

The state you get from `useReducer` is component-dependent, along with its dispatch function. This makes it impossible to use the dispatch from one `useReducer` call on a different reducer. For instance, take these two separate stores and their dispatch functions:

```jsx
const [notificationStore, dispatch1] = useReducer(initialState, notificationReducer)
const [authStore, dispatch2] = useReducer(initialState, authReducer)
```

Because of the dependence of the dispatch function on the `useReducer` call that returned it, you can’t use `dispatch1` to trigger state updates in `authStore`, nor can you use `dispatch2` to trigger state updates in `notificationStore`.

This limitation means you have to manually keep track of which dispatch function belongs to which reducer, and it may ultimately result in more bloat. As of the time of writing this article, there is no known way to combine dispatch functions or reducers.

### Extensibility

One of my favorite features of Redux is how extensible it is. For instance, you can add a logger middleware that logs all the actions dispatched, and you can use the Chrome extension to view your store and even diff changes between dispatches.

These are all things that you’d give up if you decide to replace Redux with `useReducer`. Or you could implement these yourself, but you’d be reintroducing the boilerplate that Redux brings with it.

## Conclusion

The `useReducer` hook is a pretty nice addition to the React library. It allows for a more predictable and organized way to update your component’s state and, to an extent (when coupled with useContext), makes sharing data between components a bit easier.

It has its shortcomings, too, which we discussed above, and if you find a way to get around them in an efficient way, please let me know in the comment section below.

Check out the [React documentation to learn more about this](https://reactjs.org/docs/hooks-reference.html#usereducer) and the other Hooks available right now. Happy coding!
* * * 
**Editor's note:** Seeing something wrong with this post? You can find the correct version [here](https://blog.logrocket.com/state-management-using-only-react-hooks/).

## Plug: [LogRocket](https://logrocket.com/signup/), a DVR for web apps
 
![LogRocket Dashboard Free Trial Banner](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png?resize=1200%2C677&ssl=1)
 
[LogRocket](https://logrocket.com/signup/) is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
 
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
 
[Try it for free](https://logrocket.com/signup/).
* * *
The post [State management using only React Hooks](https://blog.logrocket.com/state-management-using-only-react-hooks/) appeared first on [LogRocket Blog](https://blog.logrocket.com).