Can You Replace Redux with React Hooks?
Tags: react, redux, javascript, webdev
Published: 2019-08-26 12:28:10

*This was originally published on [chrisachard.com](https://chrisachard.com/replace-redux-with-react-hooks)*

Hooks are gaining popularity as a way to add state and effects to function components - but can they go further?  

Many people find Redux confusing or verbose - so maybe hooks could serve as simple replacement... so let's find out - *Can you replace Redux with hooks?*

**TL;DR:** Hooks do a lot, but only get 3/5 stars from me for replacing Redux: ⭐️⭐️⭐️
But! It really depends on how you use Redux

# Why use Redux?

Before we figure out if we can replace Redux, we first have to understand the problem it solves.

## 1. Shared State

The #1 reason I reach for Redux in a project is to share state between components that are far apart in the component tree.  Here's a picture of what I mean:

![Component Tree](https://thepracticaldev.s3.amazonaws.com/i/ige0qoqz9reb5fdwersy.png)

Here, the `Navbar` component holds a piece of state called `username`.  

With a regular state/props strategy, if we wanted to get that `username` into the `Messages` component - then we'd need to go up through `App`, down through `Body`, and into `Messages`.  That kind of [prop drilling](https://kentcdodds.com/blog/prop-drilling/) gets really cumbersome and verbose in large applications - so we need a way to share certain state across multiple components.

Redux fixes that by keeping a **global, shared state**, and allows us to access that state from any component by connecting to it.

## 2. Consolidate Business Logic

Another major aspect of Redux is that it allows you to centralize and (potentially) reuse your business logic.  You can do that in a few different ways:

- **Reducers** let you move your state update logic into a single place
- **Actions** with the help of `redux-thunk`, allow for async data fetching and complex logic, *before* sending that data to reducers
- **Middleware** allows you to inject custom functions in to the middle of the action/update cycle, and centralize your logic
- **Redux Sagas** let you handle long running async actions in a smooth, centralized way

## 3. Enhanced Debugging

There are two powerful tools that Redux can give you that help with debugging:

#### Redux DevTools 

As actions run through a Redux application, the changes they make to the data can be traced.  That trace is available in the [Redux DevTools](https://github.com/reduxjs/redux-devtools) (and [Redux DevTools Extension](https://github.com/zalmoxisus/redux-devtools-extension)), which lets you see all of the actions performed in your app, and how it affected the state in Redux.

![Redux DevTools Extension](https://thepracticaldev.s3.amazonaws.com/i/ud92qulm19nw98zk2pdz.png)

That let's you track everything that happens in your app - and if something isn't happening the way you think it should, you can see exactly why.  Neat!

#### Time-Travel Debugging

When you take that a step further, then you realize that you can *rewind* your actions just as easily as playing them forward - and you get **time travel!**

Going back and forward in "time" with your actions can really help for catching sticky bugs - or for catching bugs that require a lot of setup time to capture.


# What do hooks give us?

Hooks were added to React in `16.8` and in particular, there are three hooks that we might be able to combine to give us Redux functionality:


## useContext

Context existed before the `useContext` hook did, but now we have a straightforward, easy way to access context from function components.

Context allows us to lift and **share state** up to a higher component in the tree - which then allows us to share it with other components.

So if we define a shared context:

```jsx
const AppContext = React.createContext({});
```

and provide it to our app by wrapping the entire app with it:

```jsx
<AppContext.Provider value={{ username: 'superawesome' }}>
  <div className="App">
    <Navbar />
    <Messages />
  </div>
</AppContext.Provider>
```

Then we can consume that context in the child components:

```jsx
const Navbar = () => {
  const { username } = useContext(AppContext)

  return (
    <div className="navbar">
      <p>AwesomeSite</p>
      <p>{username}</p>
    </div>
  )
}
```

And that works!  It let's us share state across our entire application (if we want) - and use that state in any of our components.

<iframe src="https://codesandbox.io/embed/react-usecallback-9d78v?fontsize=14" title="react-useContext-redux" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>


## useReducer

When we get down to it, **this** is the component that had people excited about hooks possibly replacing Redux... after all - it has **`reducer`** right in the name!  But let's check out what it actually does first.

To use `useReducer`, first we define a reducer function - that can look *exactly* like one from Redux:

```jsx
const myReducer = (state, action) => {
  switch(action.type) {
    case('countUp'):
      return {
        ...state,
        count: state.count + 1
      }
    default:
      return state
  }
}
```

Then in our component, we use the `useReducer` hook, passing in that reducer function and a default state.  That returns the current `state`, and a `dispatch` function (again - just like Redux!)

```jsx
const [state, dispatch] = useReducer(myReducer, { count: 0 })
```

And finally, we can use that `state` to show the values inside, and we can use `dispatch` to change them:

```jsx
<div className="App">
  <button onClick={() => dispatch({ type: 'countUp' })}>
    +1
  </button>
  <p>Count: {state.count}</p>
</div>
```

And here's a demo of it all working:

<iframe src="https://codesandbox.io/embed/react-usecontext-redux-6z878?fontsize=14" title="react-useReducer-redux" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>


## useEffect

OK - the last thing we need then is reusable logic inside of our actions.  To accomplish that, we'll take a look at `useEffect`, and how we can write custom hooks.

`useEffect` allows us to run asynchronous actions (like http requests) inside of a function component, and it lets us re-run those actions whenever certain data changes.  

Let's take a look at an example:

```jsx
useEffect(() => {
  // Async Action
}, [dependencies])

```

This is just like a Redux action with `redux-thunk` installed.  We can run an async action, and then do whatever we'd like with the result.  For example - here we are loading from an http request, and setting that to some local state:

```jsx
const [person, setPerson] = useState({})

useEffect(() => {
  fetch(`https://swapi.co/api/people/${personId}/`)
    .then(response => response.json())
    .then(data => setPerson(data))
}, [personId])
```

And here's a demo of that working:

<iframe src="https://codesandbox.io/embed/react-useeffect-redux-p7e0s?fontsize=14" title="react-useEffect-redux" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>

And so we've re-created actions now too!


# So!

...we've made a mini Redux!... right?  

By combining `useContext` which allows us to share state across multiple components, with `useReducer` which allows us to write and share reducers *just like redux*, and with `useEffect` which allows us to write asynchronous actions and then dispatch to those reducers... that sounds a lot like Redux!

But: let's take a look at how we've done when we consider what people actually use Redux for:

### 1. Shared State

In terms of shared state, we've done pretty well.  We can use context to share a global state (that we keep in a reducer) with multiple components.  

However, we should be careful to think that Context is the answer to all of our shared state problems.  Here's a tweet from Dan Abromov (the creator of Redux) describing one of the possible downsides:

![Dan Abromov Context](https://thepracticaldev.s3.amazonaws.com/i/4z4m77cze1a5u0v1p93c.png)

[https://twitter.com/dan_abramov/status/1163051479000866816](https://twitter.com/dan_abramov/status/1163051479000866816)

So, while Redux is meant to keep your entire state (or most of it) in a globally accessible, single store - context is really designed to *only* share state that is really needed to be shared across multiple components across the component tree.

#### Shared State Score

Since it's possible (though perhaps shouldn't be your first choice) to share state with `useContext` - I'll give hooks a 4/5 stars for sharing state.

**Score:** ⭐️⭐️⭐️⭐️


### 2. Consolidate Business Logic

The main methods for consolidating business logic in Redux are in the reducers and in actions - which we can achieve with `useReducer` and `useEffect` ... hooray!

But we can't forget about Redux middleware, which some people use  heavily, and other solutions like Redux Sagas, which can provide advanced asynchronous work flow options. 

#### Business Logic Score

Since we're missing parts of Redux that some people use a lot, I have to give this a lower score: 3/5 stars.  

If you are someone who really likes middleware or sagas though - then your score would be more like 1/5 stars here.

**Score:** ⭐️⭐️⭐️


### 3. Enhanced Debugging

The one thing that hooks *don't give us at all* is any kind of enhanced debugging like Redux DevTools or time travel debugging.  

It's true, there is the `useDebugValue` hook, so you can get a little bit of debugging for custom hooks - but in general, Redux is far ahead here.

#### Debugging Score

We're missing almost everything here - so this score has to be low: 1/5 stars.

**Score:** ⭐️


# So, can we replace Redux with Hooks?

**If you use Redux only to share state across components**

Then yes! ... probably.  However, you may want to consider other options as well.  There is the famous Dan Abramov post that [You might not need Redux](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367) - so you may want to consider all of your options before you jump to trying to use `useContext` to replace all of Redux.

**If you use middleware or sagas heavily**

Then no, unless you rework how to handle your application logic.  Hooks just don't have the same control options that Redux does, unless you custom build it.

**If you really like Redux DevTools and time travel debugging**

Then definitely not, no.  Hooks don't have that ability (yet?!) so you're better off sticking with Redux.

## I should mention

Redux hasn't been sitting by and just watching hooks!  Check out these docs for [hooks in Redux](https://react-redux.js.org/next/api/hooks) and you can join the hook party, even if you use Redux!

Also, for a more complete answer comparing Redux to other options, there's a post that explains that [Redux is not dead yet](https://blog.isquaredsoftware.com/2018/03/redux-not-dead-yet/)

# Overall score

For how I use Redux, I give hooks a 3/5 stars for replacing Redux

**3/5 Stars:** ⭐️⭐️⭐️

At least - I'm going to try hooks first on my next project, before just jumping into Redux.  However, for complex projects, with multiple developers - I wouldn't rule out Redux just yet.

&nbsp; 

**Like this post?**
You can find more by:

- Following me on twitter: [@chrisachard](https://twitter.com/chrisachard)
- Joining the newsletter: [chrisachard.com](https://chrisachard.com/)

Thanks for reading!