React — Redux vs Hooks. Can we replace Redux with hooks?
By: Dan Ayasch
Tags: Hooks, React, Redux, Tech, State Management

Introduction

The state management of a React application is one of the biggest challenges you have to face when building an app. Basically, the state management represents how we can communicate and share data across all components of the application, through an app global state where we can read or write datas.

Nowadays there are so many different state management libraries that it can be complicated to choose one or another.

For many years Redux was the default choice when starting the development of a new React application, because it scales well for big applications and has a lot of benefits. Since React 16.8, some developers are trying to use React Hooks instead of Redux.

In this article, we will talk about Redux and the Hooks. Redux is not always the best choice. It depends on the application you want to build. How can you switch from Redux to Hooks?

Redux

Redux is a predictable state management tool for JS apps that behave consistently across client, server, and native environments.

Redux allows you to create a store that will handle all the state of your application. Each component can subscribe to the store to access any state that it needs, and if needed to update it.

The state management with Redux follows the Flux pattern, that proposes a unidirectional data flow, and components that have specific responsibilities.

What are the benefits of Redux?

Easy state transfer between components -> as every component can use the store it’s easier to share data between them.

Predictable state -> as reducer functions are pure, the state will remain the same.

Easy to maintain -> redux comes with a strict way to organize the code, so it makes it easier for someone that knows Redux to understand the project.

Hooks

Hooks are quite new in React as it’s from React 16.8. It allows us to have a local state and some other features without having to write a class component.

It doesn’t mean that React wants to get rid of Classes. Hooks can be used concurrently with Classes, but Hooks doesn’t work inside a class.

The Hooks has been created to fix some issues that developers usually have with class component, such as :

Wrapper hell (Split components into small pieces of component)

Huge components (Adding more logic to a component to solve Wrapper hell issue)

Confusing classes (Classes became hard to understand)

Using React Hooks as primary state management without any other libraries is possible, but depends on your understanding of the Hooks.

If you decide to use the Hooks, you’ll not need to install any external libraries, and you can use `useContext()` to handle the global state of your application.

You also will find a hook, `useReducer`, that will allow you to have a reducer that will work the same as the reducer you can have when working with Redux.

We’ll see in the example below how it works.

So can we replace Redux with Hooks?

To answer this question we will start with a simple todo app project using Redux and we’ll try to update it, to use the Hooks instead.

So let’s start with a simple app using Redux.

import React from “react”;

import ReactDOM from “react-dom”;

import “./index.css”;

import App from “./App”;

import * as serviceWorker from “./serviceWorker”; import { Provider } from “react-Redux”;

import store from “./Redux/store”; ReactDOM.render(

<Provider store={store}>

<App />

</Provider>,

document.getElementById(“root”)

); // If you want your app to work offline and load faster, you can change

// unregister() to register() below. Note this comes with some pitfalls.

// Learn more about service workers: <https://bit.ly/CRA-PWA>

serviceWorker.unregister();

So as you can see we set the react-Redux Provider so that our app can work with Redux.

Here is the initialization of the store and the main reducer:

src/Redux/store.js

import { createStore } from “Redux”;

import rootReducer from “./reducer”; export default createStore(rootReducer);

src/Redux/reducer.js



const initialState = {

todoList: []

}; export default function(state = initialState, action) {

switch (action.type) {

case “ADD_TODO”: {

return {

…state,

todoList: […state.todoList, action.payload.newTodo]

};

}

default:

return state;

}

}

Our src/App.js will look like this:

import React, { Component } from “react”; import AddTodo from “./components/AddTodo”;

import ListTodo from “./components/ListTodo”; import logo from “./logo.svg”;

import “./App.css”; class App extends Component {

render() {

return (

<div className=”App”>

<header className=”App-header”>

<img src={logo} className=”App-logo” alt=”logo” />

<p>This app has been created for the purpose of this article</p>

<AddTodo />

<ListTodo />

</header>

</div>

);

}

} export default App;

In the App file we are using two components, one that will display an input so that the user can add an element in the todo list, and the second component will display the list of todo previously added by the user.

src/components/AddTodo.js

import React, { Component } from "react";

import { connect } from "react-Redux";



import "../App.css";



class AddTodo extends Component {

constructor(props) {

super(props);



this.state = { inputValue: "" };

}



render() {

return (

<div>

<input

placeholder="Create a new todo element here"

className="App-input"

value={this.state.inputValue}

onChange={e => this.setState({ inputValue: e.target.value })}

/>

<button

className="App-button"

onClick={() => {

this.props.addTodo(this.state.inputValue);

this.setState({ inputValue: "" });

}}

>

Add todo

</button>

</div>

);

}

}



const mapDispatchToProps = dispatch => ({

addTodo: content => {

dispatch({

type: "ADD_TODO",

payload: {

newTodo: content

}

});

}

});



export default connect(null, mapDispatchToProps)(AddTodo);

And src/components/ListTodo.js

import React, { Component } from "react";

import { connect } from "react-Redux";



import "../App.css";



class ListTodo extends Component {

constructor(props) {

super(props);



this.state = { todoList: [], newTodoNotification: false };

}



static getDerivedStateFromProps(nextProps, prevState) {

if (nextProps.todoList !== prevState.todoList) {

return { todoList: nextProps.todoList };

} else return null;

}



componentDidUpdate(prevProps, prevState) {

if (prevProps.todoList !== this.props.todoList) {

this.setState({

todoList: this.props.someValue,

newTodoNotification: true

});

setTimeout(() => this.setState({ newTodoNotification: false }), 3000);

}

}



render() {

return (

<div style={{ marginTop: 20 }}>

<p

style={

this.state.todoList.length > 0 && this.state.newTodoNotification

? { display: "block", color: 'green' }

: { display: "none" }

}

>

A NEW ELEMENT HAS BEEN ADDED!!

</p>



{this.state.todoList.map((todo, key) => (

<p key={key} style={{ margin: 0, marginTop: 5 }}>{todo}</p>

))}

</div>

);

}

}



const mapStateToProps = (state, ownProps) => ({

...state

});



export default connect(mapStateToProps)(ListTodo);

The goal of this article is not to explain how Redux works, but how to replace Redux by Hooks.

One of the biggest changes we’ll have when using Hooks to replace Redux is that with Redux we have one unique store for our entire app, with the Hooks we’ll have the possibility to have one store per app part.

For the example of the todo app, we’ll have only one store so it will works exactly how Redux works.

So let’s update our code, so that it won’t use Redux anymore.

First, we have to update our index.js so that we update the provider

src/index.js

import React from "react";

import ReactDOM from "react-dom";

import "./index.css";

import App from "./App";

import * as serviceWorker from "./serviceWorker";



import { Provider } from "react-Redux";

import store from "./Redux/store";



import { AppProvider } from "./ContextProvier";

import { initialState, reducer } from "./hookReducer";



ReactDOM.render(

<AppProvider initialState={initialState} reducer={reducer}>

<App />

</AppProvider>,

document.getElementById("root")

);



// If you want your app to work offline and load faster, you can change

// unregister() to register() below. Note this comes with some pitfalls.

// Learn more about service workers: <https://bit.ly/CRA-PWA>

serviceWorker.unregister();

We created a component called AppProvider, that we’ll allow us to have a Context to store the data we need.

src/ContextProvider.js

import React, { createContext, useReducer, useContext } from 'react';



const AppContext = createContext();



export const AppProvider = ({ reducer, initialState, children }) => (

<AppContext.Provider value={useReducer(reducer, initialState)}>{children}</AppContext.Provider>

);



export const useAppState = () => useContext(AppContext);

And our hookReducer src/hookReducer.js

export const initialState = {

todoList: []

};



export const reducer = (state = initialState, action) => {

switch (action.type) {

case "ADD_TODO": {

return {

...state,

todoList: [...state.todoList, action.payload.newTodo]

};

}

default:

return state;

}

};

As you can see, this reducer looks exactly the same as the one we had with Redux.

Our src/App.js will work exactly the same way it was, we will just call 2 new components that are using Hooks instead of Redux.

import React, { Component } from "react";



import HookAddTodo from "./components/HookAddTodo";

import HookListTodo from "./components/HookListTodo";



import logo from "./logo.svg";

import "./App.css";



class App extends Component {

render() {

return (

<div className="App">

<header className="App-header">

<img src={logo} className="App-logo" alt="logo" />

<p>This app has been created for the purpose of this article.</p>

<HookAddTodo />

<HookListTodo />

</header>

</div>

);

}

} export default App;

And our 2 new components

src/HookAddTodo.js

import React, { useState } from "react";



import "../App.css";

import { useAppState } from "../ContextProvier";



const HookAddTodo = () => {

const [, dispatch] = useAppState();

const [inputValue, setInputValue] = useState("");



return (

<div>

<input

placeholder="Create a new todo element here"

className="App-input"

value={inputValue}

onChange={e => setInputValue(e.target.value)}

/>

<button

className="App-button"

onClick={() => {

dispatch({ type: "ADD_TODO", payload: { newTodo: inputValue } });

setInputValue('')

}}

>

Add todo

</button>

</div>

);

};



export default HookAddTodo;

src/HookListTodo.js

import React, { useEffect, useState } from "react";



import "../App.css";

import { useAppState } from "../ContextProvier";



const HookListTodo = () => {

const [{ todoList }] = useAppState();

const [newTodoNotification, setNewTodoNotification] = useState(false);



useEffect(() => {

setNewTodoNotification(true);

setTimeout(() => setNewTodoNotification(false), 3000);

}, [todoList]);



return (

<div style={{ marginTop: 20 }}>

<p

style={

todoList.length > 0 && newTodoNotification

? { display: "block", color: "green" }

: { display: "none" }

}

>

A NEW ELEMENT HAS BEEN ADDED!!

</p>

{todoList.map(todo => (

<p style={{ margin: 0, marginTop: 5 }}>{todo}</p>

))}

</div>

);

};



export default HookListTodo;

If we want to compare those 2 functional components and the two components we had with Redux, it’s quite simple.

First we are not using class components anymore, as explained in this article, Hooks cannot be used inside Class components and MUST be inside functional components.

Then we removed all the code related to Redux, and we replaced it with the context provider we created, and use the dispatch function provided by the hook useContext().

The dispatch function of the useContext will work exactly as the one of Redux. It takes an object that must contain a type, corresponding to the action to dispatch. This action will be received in the reducer, that will save it in the store of the current context. What’s really different with Redux, is that we can have multiple context in our app so multiple store.

Conclusion

First of all, as we saw in this article, replacing Redux by Hooks in a React project is possible. The question is, is it always the right solution?

If you already have a big project using Redux with middleware or sagas heavily, then you should probably not move to Hooks and stick to Redux.

Hooks are also possible with Redux, and it could be a good way to keep Redux, so check out the Hooks in Redux documentation https://react-redux.js.org/api/hooks.

For more explanation on this, you should read the article https://medium.com/@dan_abramov/you-might-not-need-Redux-be46360cf367 written by Dan Abramov, the creator of Redux.