Dont’t go by the main head cover image, it is not the pattern you think about!!
What’s a pattern in redux? The same question I stumbled upon when I started to learn this particular topic. As the day 10 stands, we are here to learn about Redux patterns and its practical example.
The Redux pattern is a design pattern for managing the state of an application, especially helpful in complex, large-scale applications. It was initially inspired by the Flux architecture from Facebook but has its own set of principles and structures. Redux is particularly popular in React applications, but it can be used with any JavaScript framework.
Let’s break down the basics of the Redux pattern in a way that's easy to grasp. It might be covered in previous blog posts and consider it as like a repeat telecast on television.
Components are the UI parts of your application, where users interact with buttons, inputs, etc. When a user performs an action (like clicking a button), the component will trigger an action in Redux.
An action is a plain JavaScript object that describes a change you want to make to the state. Actions typically have a type
property (describing the type of action) and may have a payload
property (providing any data needed to make the change). For example, an action for adding an item might look like this: { type: 'ADD_ITEM', payload: { id: 1, name: 'Apple' } }
.
A reducer is a pure function that takes the current state and an action, and returns a new state based on the action type. When an action is dispatched, it flows through the reducers to calculate the new state. For example, if an ADD_ITEM
action is dispatched, the reducer might update the list of items in the state.
As in above image, we might be missing one of the key components and that is Store. The store is basically the central location that holds the entire state of the application. When the reducers return a new state, it updates the store, which in turn notifies the components that the state has changed.
And lastly the server. In some cases, actions involve asynchronous operations, such as fetching data from or sending data to a server. Middleware like Redux Thunk or Redux Saga can intercept actions and handle the asynchronous part, such as making a network request to the server. After the server responds, a new action is dispatched to update the state based on the server's response.
Simplifying the flow of data:
Component → Action → Reducer → Store → Component → Server (optional in case of involvement of Middleware).
Lets take a look at Redux Selectors, Shall we?
Selectors are functions that take the entire Redux state and return a specific piece of that state. They’re useful for keeping your components clean and separating logic that selects or transforms data from your Redux store.
To keep things simple; in Redux, a selector is just a function that pulls specific data from the Redux store. It helps simplify data retrieval and keeps your components clean by not exposing the structure of your entire state.
Let us understand with simplest possible example. Imagine your state holds data about users. Here’s a simple Redux state:
const initialState = {
users: [
{ id: 1, name: "Alice", age: 25 },
{ id: 2, name: "Bob", age: 30 },
{ id: 3, name: "Charlie", age: 35 },
],
};
Now we will create a selector.
const selectUsers = (state) => state.users;
This selectUsers will be our selector. This selector function simply returns the users
array from the state. Now, instead of accessing state.users
directly in a component, we use this selector to make our code more organized.
import { useSelector } from "react-redux";
function UserList() {
// useSelector calls our selector to get the users data
const users = useSelector(selectUsers);
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Here we write component that uses selectUsers
to get data from the store. In above example, useSelector(selectUsers)
gives us access to users
in the Redux state.
Let go one level up, shall we?
How about using reselect
for Memoisation. The question which might ring in your minds is what exactly is the need of reselect for Memoisation? Well, that’s what we are about to find out.
When managing application state with Redux, you may encounter scenarios where deriving data from the state becomes computationally expensive, especially as the size of your state increases. In such cases, Reselect
provides memoization, which ensures that derived data is recomputed only when its inputs have changed.
By avoiding complex re-calculations, reselect ensures that output is always cached and computation only happens when the relevant input changes. In Redux, state changes can cause components to re-render and re-evaluate derived data even if the part of the state they depend on hasn't changed. Without memoization, every state change could recompute derived values unnecessarily, reducing performance.
npm install reselect // Installing reselect
Take an example like we want a selector that returns only users aged 30 or older.
// STEP 1 : Import createSelector from reselect
import { createSelector } from 'reselect';
// STEP 2 : A basic selector for users
const selectUsers = (state) => state.users;
// STEP 3 : Using createSelector to create a Memoized selector
const selectOlderUsers = createSelector(
[selectUsers],
(users) => users.filter(user => user.age >= 30)
);
In above example, selectOlderUsers
will only recompute when the users
array changes. This reduces unnecessary recalculations, improving app performance.
For this to be functional, it is important to write a function in a react component.
function OlderUserList() {
const olderUsers = useSelector(selectOlderUsers);
return (
<ul>
{olderUsers.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Now, OlderUserList
only renders users aged 30 and up, and it efficiently updates when needed.
One of the very very best practices…
Normalized State
Well, as the content of the day focuses on advanced Redux concepts (YOU MAY READ TITLE IF YOU MISSED IT!), including best practices for managing state in a large-scale application. Topics like normalized state are critical because they ensure:
Scalability
Maintainability
Efficiency
Scalable, beacuse as the app grows, normalized state prevents performance bottlenecks. Maintainable, as it becomes easier to understand and manage the state tree. I also mentioned efficiency as updating and fetching data becomes faster due to direct access using keys.
In Redux, normalized state refers to a structured and organized way of storing application data in the Redux store. It ensures that the state is stored in a flat, non-nested structure, with entities (e.g., users, posts, comments) identified by unique keys (usually id
s). This approach avoids duplication, simplifies updates, and improves performance when dealing with relational or nested data.
We are using libraries like Normalizr to implement above concept.
npm install normalizr
import { schema, normalize } from "normalizr";
const author = new schema.Entity("authors");
const post = new schema.Entity("posts", {
author: author,
});
const data = [
{
id: 1,
title: "Learn Redux",
author: { id: 1, name: "Alice" },
},
{
id: 2,
title: "Learn Normalization",
author: { id: 1, name: "Alice" },
},
];
const normalizedData = normalize(data, [post]);
console.log(normalizedData);
/*
Output:
{
entities: {
authors: {
1: { id: 1, name: "Alice" },
},
posts: {
1: { id: 1, title: "Learn Redux", author: 1 },
2: { id: 2, title: "Learn Normalization", author: 1 },
},
},
result: [1, 2],
}
*/
Keeping things simple:
Normalized state is an essential concept in Redux for applications that deal with relational or nested data. It ensures:
No duplication or inconsistency in the state.
Simplified state updates and better performance.
Easier relational data handling.
Well that concludes this day’s learning. We will be exploring more of the advanced patterns and new content in further days to come. Keep following up with this series to get acquainted with it.
Until then Ciao!