State Management

As applications grow in complexity, managing state becomes increasingly challenging. We will discuss their features, advantages, and use cases to help you choose the right one for your project.
Redux is a predictable state container for JavaScript applications. It follows a unidirectional data flow and is based on the principles of immutability and pure functions. Redux provides a centralized store for managing application state, making it easier to debug and test.
The main components of Redux include:
In Redux we can have different types of actions, such as synchronous actions, asynchronous actions:
The Bellow diagram illustrates the flow of data in a Redux application:

This feels a bit over-engineered. Is there a simpler way?
Here comes Zustand, a small, fast and scalable bearbones state-management solution. It provides a simple API for managing state in React applications without the need for boilerplate code or complex configurations.
Zustand uses a hook-based approach to manage state. It allows you to create a store that holds your application state and provides methods to update and access that state. Zustand is designed to be flexible.
The Bellow diagram illustrates the flow of data in a Zustand application:

So if we have such a simple state management solution, why do we need Redux?
Benefit of Redux:
So if we have a big team and prefer to have sth like backend event driven architecture, domain driven design, or we want to have a better debugging experience, Redux can be a great choice.
Also Redux introduces RTK Query, a powerful data fetching and caching tool that simplifies the process of managing server state in your application. It provides features like automatic caching, request deduplication, and built-in support for optimistic updates, making it easier to handle asynchronous data fetching and synchronization with the server.
The Bellow diagram illustrates the flow of RTK Query in a Redux application:

This is an example of how to use RTK Query to manage server state in a Redux application:
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; export interface Product { id: number; name: string; price: number; } export interface CreateProductDto { name: string; price: number; } export const api = createApi({ reducerPath: "api", baseQuery: fetchBaseQuery({ baseUrl: "https://api.example.com", }), tagTypes: ["Product"], endpoints: (builder) => ({ // GET /products getProducts: builder.query<Product[], void>({ query: () => "/products", providesTags: ["Product"], }), // GET /products/:id getProduct: builder.query<Product, number>({ query: (id) => `/products/${id}`, providesTags: (_result, _error, id) => [{ type: "Product", id }], }), // POST /products createProduct: builder.mutation<Product, CreateProductDto>({ query: (body) => ({ url: "/products", method: "POST", body, }), invalidatesTags: ["Product"], }), // DELETE /products/:id deleteProduct: builder.mutation<void, number>({ query: (id) => ({ url: `/products/${id}`, method: "DELETE", }), invalidatesTags: ["Product"], }), }), }); // auto-generated hooks export const { useGetProductsQuery, useGetProductQuery, useCreateProductMutation, useDeleteProductMutation, } = api;
Beside the Rest API, RTK Query also supports GraphQL and WebSocket APIs, which makes it a versatile choice for handling various types of data fetching scenarios in modern applications.
// WebSocket API example getOrders: builder.query<Order[], void>({ query: () => "/orders", async onCacheEntryAdded(arg, { updateCachedData, cacheEntryRemoved }) { const socket = new WebSocket("ws://localhost:3000"); socket.onmessage = (event) => { const data = JSON.parse(event.data); updateCachedData((draft) => { draft.push(data); }); }; await cacheEntryRemoved; socket.close(); }, });
As you see handling WebSocket connections with RTK Query is much simpler than using saga and channel in Redux, which is very complex and requires a lot of boilerplate code.
when we have a small/medium project, we want higher performance and simplicity to implement, or we are working on realtime/streaming applications, Zustand can be a great choice.
Sample use cases for Zustand include:
So Redux is more suitable for larger applications with complex state management needs, while Zustand is a great choice for smaller applications or when you want a simpler solution. Ultimately, the choice between Redux and Zustand depends on the specific requirements of your project and your preferences as a developer.
As always, I may miss some points, so if you have any questions or suggestions, please feel free to share them with me. If you know a better approach or library for handling state management, I’d love to hear about it. I’m always open to learning and improving my knowledge.
Stay humble and keep learning!