Introduction
State management is critical when building front-end applications with React. As your application gets more complex, it becomes crucial to organize your app state and keep the data flows between your components simple.
A common approach in managing global state is lifting the state up to the parent component and manually passing it down through several levels to the child component via props. This approach, known as prop drilling, can lead to unnecessary re-renders of the child components when the state changes.
A better way to manage global state is through the use of React Context or third-party state management libraries such as Redux, Zustand, Mobx, and Recoil. In this article, I will examine Redux and Zustand, comparing their features, ease of use, and performance.
Redux
Redux is a JavaScript library for managing and updating application state, using events called actions. It serves as a centralized store for the application state, with rules ensuring the state is updated predictably and consistently. Redux is tiny (1.4kb) and can be used with React or any other view library. Due to the amount of setup and boilerplate code required by Redux. @reduxjs/toolkit has been recommended by the Redux team as the opinionated approach to efficient redux management.
Usage
To get started with Redux. Install the redux toolkit and react-redux library for bindings to React.
npm install @reduxjs/toolkit react-redux
Afterward, create a store. I will be using a counter store example.
import { createSlice, configureStore } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
incremented: state => {
state.value += 1
},
decremented: state => {
state.value -= 1
}
}
})
export const { incremented, decremented } = counterSlice.actions
const store = configureStore({
reducer: counterSlice.reducer
})
Redux uses reducers, which are functions that receive the current state and an action object and return a new state based on the action provided. Redux toolkit simplifies this by providing the createSlice
function, which handles the state for a specific feature in the application, and returns the actions and reducers for the slice.
If you notice, the reducer directly updates the state, rather than returning a new state. This is because createSlice
function uses immer library internally. Immer is a library that makes it easy to update immutable state.
The slice reducer is passed to the configureStore
function to create the global store, which is used in the React application.
To consume the store in react, use the Provider
component from react-redux
package to expose the store to the entire component tree:
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import store from './store'
import App from './App'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Provider store={store}>
<App />
</Provider>,
)
Using the store in a Counter
component:
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import {
decremented,
incremented,
} from './../store'
export function Counter() {
const count = useSelector((state) => state.counter.value)
const dispatch = useDispatch()
return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(incremented())}
>
+
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decremented())}
>
-
</button>
</div>
</div>
)
}
The Counter
component displays the count from the store and whenever an action is dispatched, the store is correspondingly updated and the Counter component is re-rendered.
To learn more about redux, check out their documentation.
Zustand
Zustand is a small, unopinionated state management library. It offers a simple API to manage the application state using react hooks.
Usage
To get started, install Zustand.
npm install zustand
I will use the same counter state example to show creating a store with Zustand.
import { create } from "zustand";
export const useCounter = create((set) => ({
count: 0,
incremented: () => set((state) => ({ count: state.count + 1 })),
decremented: () => set((state) => ({ count: state.count - 1 })),
}))
The create function returns a hook which is used directly in a react component to select and update the store.
Using the hook in the Counter
component:
import React from 'react'
import { useCounter } from './../store'
export function Counter() {
const count = useCounter((state) => state.count)
const [incremented, decremented] = useCounter((state) => [state.incremented, state.decremented])
return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => incremented()}
>
+
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => decremented()}
>
-
</button>
</div>
</div>
)
}
Redux vs Zustand
Zustand and Redux are very similar, both are based on the immutable state model, where every new action creates a new state with updated fields rather than modifying the current state. However, there are some differences:
React Context
Redux requires the use of a context provider to make the state available to the rest of the app. On the other hand, Zustand exposes a hook that returns the the global state when called.
Ease of Use
Zustand has a very simple API to create and consume stores, making it ideal for small and medium-sized projects where velocity matters. Redux has a more complex setup and a larger API which provides an opinionated and more structured approach for managing application state. This makes redux suitable for larger and more complex applications where a structured approach is preferred. Redux also has a steeper learning curve due to its complex concepts and larger API surface.
Size
In terms of bundle size, Zustand(1.2kb) and Redux (1.4kb) have very little difference. But if @reduxjs/toolkit
is used (which is recommended), the bundle size increases to 13.6kb.
Ecosystem
Redux has a mature ecosystem with a wide range of middleware and developer tools available. Zustand being newer has a smaller ecosystem.
Conclusion
In conclusion, both Redux and Zustand have their strengths and weaknesses and the choice between both depends on your project's complexity and personal preference. Consider the tradeoffs and choose the one that aligns with your project goals and your expertise.
HNG
I am currently enrolled in the HNG internship program. The HNG Internship offers exceptional opportunities for growth and exposure in the tech industry. For more information on the program and how it empowers aspiring developers, visit the HNG Internship and look into potential collaborations or recruitments through HNG Hire.