React Hooks vs. Redux in 2024

TL;DR: This blog discusses how React Hooks and Redux differ in managing the state of React apps. React Hooks excels in simpler projects and component-level states, while Redux is better for complex, large-scale applications. Get insights into their core concepts and best practices for combining both approaches.

Overview of React Hooks and Redux

In this article, we will look at state management in React.

Redux and React are two of the most popular libraries for building modern web applications.

There are many different Hooks in React.

This article explores the difference between React Hooks and Redux in 2024.

Why this comparison is relevant in 2024

The front-end ecosystem is ever-evolving. As React evolves, developers face choices about managing the state of their applications.

Redux and React Hooks are two of the most popular programming languages for building web and mobile applications.

Understanding levels of state management

There are three types of state management in any modern web application:

In React, you can use the module-level state, the component-level state, and the component-level state.

Let’s see an example of different React Hooks and Redux and then compare them.

What are React Hooks?

In this tutorial, we will learn how to create React Hooks.

There are many built-in hooks available in React, and you can also create different custom hooks. However, we will explore the four hooks that are important for state management.

useState()

We define the useState Hook with a default value, and it returns an array of values, where the first value is the state, and the second value is a function to update the state.

Variables cannot be used to store the values as they get overridden once the component re-renders. React provides the Hook to persist the state.

import React, { useState } from "react";
const Counter = () => {
  const [count, setCount] = useState(0);
  return (
    <main>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </main>
  );
}

Once the value of the useState() Hook changes, the component re-renders, and the value is rendered or displayed in the browser.

useEffect()

The useEffect() is a lifecycle Hook which is invoked at three different stages in a component:

This Hook is crucial, as it allows tracking the component lifecycle, such as making network calls to fetch data from a server when the component mounts.

This record then can be stored using the useState() Hook.

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

function FetchData() {
  const [data, setData] = useState(null);

  useEffect(() => {
	fetch("https://jsonplaceholder.typicode.com/todos")
  	// convert the raw data to JSON
  	.then((response) => response.json())
  	// then set the data
  	.then((data) => setData(data));
  }, []); // Empty array means this effect runs once on mount

  return (
	<div>
  	{data ? <pre>{JSON.stringify(data, null, 2)}</pre> : "Loading..."}
	</div>
  );
}

useContext()

The useContext() creates a boundary that provides all the components within the context that can access the state directly rather than having to be passed down in the component tree.

This helps optimize and remove the middleman’s access, who does not even consume the props but rather just passes them down further.

//FeatureFlag.js
import React, { useState } from "react";

// Split test context
export const SplitTestFlag = React.createContext({});

// split text provider
export const SplitTestFlagProvider = ({ children }) => {
  const [features, setFeatures] = useState({
	darkMode: true,
	chatEnabled: false
  });

  return (
	<SplitTestFlag.Provider value={{ features, setFeatures }}>
  	  {children}
	</SplitTestFlag.Provider>
  );
};

// Component to conditionally render feature
const Feature = ({ feature, children, value }) => {
  const { features } = React.useContext(SplitTestFlag);
  return features[feature] === value ? children : null;
};

// Example
const Example = () => {
  const { features, setFeatures } = React.useContext(SplitTestFlag);
  return (
	<>
  	<Feature feature="darkMode" value={true}>
    	  in Dark Mode
  	</Feature>
  	<Feature feature="chatEnabled" value={true}>
    	  Chat
  	</Feature>
  	<button onClick={() => setFeatures({ ...features, chatEnabled: true })}>
    	  Enable Chat
  	</button>
	</>
  );
};

export default function App() {
  return (
	<SplitTestFlagProvider>
  	  <Example />
	</SplitTestFlagProvider>
  );
}

useReducer()

The useReducer is a library for managing nested objects in React.

import React, { useReducer } from "react";
const initialState = { count: 0 };
function reducer(state, action) {
  switch (action.type) {
	case "increment_count":
  	return { count: state.count + 1 };
	case "decrement_count":
  	return { count: state.count - 1 };
	default:
  	throw new Error("Invalid action");
  }
}
function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
	<div>
  	<p>Count: {state.count}</p>
  	<button onClick={() => dispatch({ type: "increment_count" })}>+</button>
  	<button onClick={() => dispatch({ type: "decrement_count" })}>-</button>
	</div>
  );
}

export default function App() {
  return <Counter />;
}

Advantages of using Hooks

Common use cases for Hooks

Understanding Redux

What is Redux?

Redux is a declarative state container for managing JavaScript apps.

Core Concepts: Actions, Reducers, Store

Actions

Actions describe what has happened. They are pure JavaScript functions that return a plain JavaScript object containing an identifier and the payload.

Identifiers help trace what state has to be changed, and the payload is the value that needs to be set to that state, which can be processed further. Refer to the following code example.

const increment = () => ({ type: 'INCREMENT', payload: null });
const decrement = () => ({ type: 'DECREMENT', payload: null });

Reducers

The BBC's Get Inspired programme looks at the role of the Reducer.

A reducer is pure JavaScript with a switch case or if-else block to determine the actions and their course. Refer to the following code example.

const initialState = { count: 0 };
function counterReducer(state = initialState, action) {
  switch (action.type) {
	case "INCREMENT":
  	return { count: state.count + 1 };
	case "DECREMENT":
  	return { count: state.count - 1 };
	default:
  	return state;
  }
}

Store

This is the centralized data manager that stores the state. To better manage the state, we can define and manage one or more states in Redux.

import { createStore } from 'redux';
const store = createStore(counterReducer);

Multiple reducers can be combined. Refer to the following code example.

import { combineReducers } from '@reduxjs/toolkit'
import todos from './todos'
import counter from './counter'

export default combineReducers({
  todos,
  counter
});

Advantages of using Redux

Common use cases for Redux

React Hooks vs. Redux: Comparative Analysis

State management

React Hooks: Ideal for managing local component state and side effects. If state management complexity is relatively low in smaller apps, Hooks works well. Refer to the following code example.

function Counter() {
  const [count, setCount] = useState(0);
  return (
	<div>
  	<p>You clicked {count} times</p>
  	<button onClick={() => setCount(count + 1)}>Click me</button>
	</div>
  );
}

Redux: Best suited for large-scale applications where global state management, predictability, and robust debugging tools are essential. Refer to the following code example.

import { createStore } from "redux";
const initialState = { count: 0 };
function counterReducer(state = initialState, action) {
  switch (action.type) {
	case "INCREMENT":
  	return { count: state.count + 1 };
	case "DECREMENT":
  	return { count: state.count - 1 };
	default:
  	return state;
  }
}
const store = createStore(counterReducer);

Side effects handling

React Hooks: Handles side effects using useEffect().

useEffect(() => {
  // Perform side effects here
  return () => {
	// Cleanup if necessary
  };
}, [dependencies]);

Redux: Uses middleware like redux-thunk or redux-saga to manage side effects.

const thunkMiddleware = (store) => (next) => (action) => {
  if (typeof action === "function") {
	return action(store.dispatch, store.getState);
  }
  return next(action);
};

Code complexity and boilerplate

In this article, you will learn how to use React Hooks in a large application.

const [state, setState] = useState(initialState);
useEffect(() => {
// Side effect logic
}, []);

Redux: Introduces more boilerplate code due to actions, reducers, and the store setup. This can lead to verbose and complex code, especially in large applications. Refer to the following code example.

const increment = () => ({ type: "INCREMENT" });
const decrement = () => ({ type: "DECREMENT" });
function counterReducer(state = initialState, action) {
  switch (action.type) {
	case "INCREMENT":
  	return { count: state.count + 1 };
	case "DECREMENT":
  	return { count: state.count - 1 };
	default:
  	return state;
  }
}

Scalability

React Hooks: Managing the state with Hooks in large applications can become challenging without proper organization and conventions. Custom Hooks can help, but they require careful design.

function useCustomHook() {
  const [state, setState] = useState(initialState);
  // Custom hook logic
  return [state, setState];
}

Redux is an open-source distributed state management platform.

const rootReducer = combineReducers({
// Combine multiple reducers
});

const store = createStore(rootReducer, applyMiddleware(thunkMiddleware));

Performance

React Hooks: Functional components with Hooks can offer better performance due to reduced overhead. Hooks also enable fine-grained control over when and how components re-render.

const [state, setState] = useState(initialState);
useEffect(() => {
  // Side effect logic
}, [dependencies]);

Redux and Middleware are two approaches to managing actions, reducers, and the store.

const store = createStore(counterReducer, applyMiddleware(thunkMiddleware));

When to use React Hooks

function Toggle() {
  const [isOn, setIsOn] = useState(false);
  return <button onClick={() => setIsOn(!isOn)}>{isOn ? "On" : "Off"}</button>;
}

When to use Redux

import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
const initialState = { data: null };
function dataReducer(state = initialState, action) {
  switch (action.type) {
	case "SET_DATA":
  	return { ...state, data: action.payload };
	default:
  	return state;
  }
}
const store = createStore(dataReducer, applyMiddleware(thunk));

Integration: Combining React Hooks and Redux

Refer to the following code example.

import React, { useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { fetchData } from "./actions";
function DataComponent() {
  const [localState, setLocalState] = useState(null);
  const globalState = useSelector((state) => state.data);
  const dispatch = useDispatch();
  useEffect(() => {
	dispatch(fetchData());
  }, [dispatch]);
  return (
	<div>
  	<button onClick={() => setLocalState("Updated Local State")}>
    	Update Local State
  	</button>
  	<p>Local State: {localState}</p>
  	<p>Global State: {JSON.stringify(globalState)}</p>
	</div>
  );
}

Performance considerations

Conclusion

Redux and React Hooks will be phased out by the end of the decade.

Redux and React Hooks are two of the most popular programming languages for building web and mobile applications.

Ref: React Hooks vs. Redux in 2024