Replacing Redux with Context and Hooks

Heads up! Do not use this in production.

Update 22.02.2019: You can use it in production. Kent C Dodds came up with a better implementation to play nicer with Context

When React Context got its new and official API, I was very skeptical about Redux dying; Now, with React Hooks, I am starting to have a second thought.

I decided to create an application using React Hooks and React Context to build an architecture similar to Redux, and assert how similar they could get to be, as well as how suitable the new tools are for the job.

TL:DR;

React Hooks are convenient

Yes, They looked very weird to me at first, and to be honest, it is still the feature I am the least excited about after the React Conf.

However, once you start using them, you become productive very quickly, and I found that I stopped worrying about what does React needs to properly work (in terms of lifecycles); to just declaring my logic and let React make its magic.

Plus, React Hooks do not introduce breaking changes, nor new concepts, and luckily, no more nested components to share logic and states as HoCs or Render Props needed.

React.useReducer

As part of the React Hooks RFC, the React.useReduce brings us the possibility to declare changes in the state as the result of a function that receives an action describing the change:

    (state, action) => newState

If you are familiar with Flux-like patterns like Redux, you already know this.

Now with this Hook as part of the React library, you might not need to use other libraries for state management as the React team will support this way of declaring changes to the state of the application.

How is React.Context important here?

React.useReducer will provide us a way to declare changes and mutate our state, as we do with Redux, however, we still need a way to easily connect our deepest children with our states, without falling in prop drilling.

Context API is a built-in way to expose data through components without passing props down.

Therefore, what if we wrap our main component to handle the application’s state with React.useReducer and we pass down the state using React.Context?

Show me the code

This is how the Store component ends up being like:

Store Component

See full version on GitHub.com/jonalvarezz

What’s cool about it?

  • We are using useReducer to set our initial state
  • The state will be passed down with Context in <StoreContext.Provider value={{ ...state, dispatch }}>
  • We are passing down the dispatch method to allow children to trigger actions
  • We are using the useEffect hook to call our API and set some data in the store.

To achieve that, I created a file structure that may result very familiar to you:

File structure

What’s cool about it?

  • If you worked with Flux-like architectures, this will be familiar to you.
  • Same concepts.
  • Other than React Context and Hooks, which have a very short-term cognitive cost to learn (really!), there are no added concepts.

Now, I can use the Store component, which is a Context.Provider Component, to pass down the reducer state. Thus, I can wrap my application:

Wrapping the app with Store Component

See full version on GitHub.com/jonalvarezz

To finally, being able to connect children components to get whatever data they need, and make’em able to dispatch actions to mutate the store!

Connecting with the Store

See full version on GitHub.com/jonalvarezz

What’s cool about it?

  • No more HoCs nor nested logic to connect to the store, not even the need to fiddle around with React’s lifecycles
  • We can fire actions that mutate the store from children easily
  • Small and plain Components

Conclusions

  • Even though this architecture may result handy, it will be limited to React-only applications. Redux can be used with any other library.

  • Same concepts can be implemented. I used concepts like selectors and actions creators. You can go further and wrap the dispatch function in order to accept Promises and other functions, same as Redux Middleware.

  • Your code is less bloated. Using Hooks instead of HoCs and Render Props does really help you to keep your components plain.

  • You lose Redux DevTools. That tool is great for debugging, and if Hooks make its way into React 17, it’ll be a matter of time for that kind of tools to be integrated with React.useReducer.

    In the meantime, if you have the React extension installed, inspecting a React.useReducer powered Component, you get very useful information and details to debug your actions and app’s state. It is like a tiny packed-up Redux Devtool.

Inspecting a component with useReducer

  • About Hooks. Although I disliked the idea of introducing magic to React’s API at first, once I tried them I started to enjoy using React without worrying about lifecycles.

    Even though, I strongly suggest newcomers to deeply learn about React’s lifecycles, as at the end, the right use of a Hook, highly depends on your understanding of React.

    For instance, take a look to the React.useEffect second parameter. You can easily miss it being new to React and end up with a lot of unnecessary re-renders and effects.

useEffect Hook's second parameter

See full version on GitHub.com/jonalvarezz

  • And finally, we are about to enter a new era of React’s rich diversity. Do you remember the hype when Flux came out? Well… you better sit down, grab some popcorn and enjoy the JavaScript fatigue.