How to Get Started with Redux for JavaScript State Management

Redux logo

Redux is a state management tool, specially designed for client-side JavaScript applications that rely heavily on complex data and external APIs, and provides excellent development tools that make your data easier to work with.

What does Redux do?

Simply put, Redux is a centralized data store. All of your app’s data is stored in a single large object. the Redux Devtools make this easy to visualize:

A visualized Redux data store

This state is immutable, which is a strange concept at first, but makes sense for several reasons. If you want to change the state, you need to send an action, which basically takes a few arguments, forms a payload, and sends it to Redux. Redux passes the current state to a collapse function, which changes the existing state and returns a new state that replaces the current state and triggers a reload of the affected components. For example, you can have a reducer to add a new item to a list, or remove or edit an item that already exists.

By doing this, you will never get undefined behavior with your app changing state at will. Additionally, since there is a record of each action and what it has changed, this enables debugging over time, where you can scroll through your app’s state to debug what is happening with each action (a much like a git history).

A record of each action

Redux can be used with any frontend framework, but it’s commonly used with React, and that’s what we’ll be focusing on here. Under the hood, Redux uses React Context API, which works the same as Redux and is suitable for simple apps if you want to forgo Redux altogether. However, Redux’s Devtools are fantastic when working with complex data, and they’re actually more optimized to avoid unnecessary reruns.

If you are using TypeScript, things are much more complicated for Redux to be strictly typed. You will want to follow this guide instead, who uses safe-actions to manage actions and reducers in a user-friendly way.

Structure your project

First off, you’ll want to layout your folder structure. It depends on you and your team’s style preferences, but there are basically two main templates that most Redux projects use. The first is to simply split each type of file (action, reducer, middleware, side effect) into its own folder, like this:

shop/
Actions /
reducers /
sagas /
middleware /
index.js

This isn’t the best, though, as you’ll often need both an action file and a shrink file for each feature you add. It is better to merge the folders of actions and reducers and divide them by functionality. This way, each action and the corresponding reducer are in the same file. You

shop/
features /
make/
etc /
sagas /
middleware /
root-reducer.js
root-action.js
index.js

This cleans up imports, because now you can import actions and reducers in the same statement using:

import {todosActions, todosReducer} from ‘store / features / todos’

It’s up to you to decide whether you want to keep the Redux code in its own folder (/ store in the examples above) or embed it in your app’s src root folder. If you already separate code by component and write lots of custom and reducer actions for each component, you might want to merge the / features / and / components / folders and store the JSX components with the reducer code.

If you are using Redux with TypeScript, you can add an additional file in each entity folder to define your types.

Installation and configuration of Redux

Install Redux and React-Redux from NPM:

npm install redux react-redux

You will probably also want redux-devtools:

npm install –save-dev redux-devtools

The first thing you’ll want to create is your store. Save it as /store/index.js

import {createStore} from ‘redux’
import rootReducer from ‘./root-reducer’

const store = createStore (rootReducer)

export the default store;

Of course, your store will get more complicated than that as you add things like side effect addons, middleware, and other utilities like responsive-connected-router, but that’s all that’s needed for now. This file takes the root reducer and calls createStore () using it, which is exported for the application to use.

Next, we’ll create a simple to-do list feature. You will probably want to start by defining the actions required by this feature and the arguments passed to them. Create a / features / todos / folder and save the following as types.js:

export const ADD = ‘ADD_TODO’
export const DELETE = ‘DELETE_TODO’
export const EDIT = ‘EDIT_TODO’

This defines some string constants for action names. Whatever data you pass, each action will have a type property, which is a unique string that identifies the action.

You don’t have to have a file of type like this, as you can just type the name of the action string, but it’s better for interoperability to do it that way. For example, you can have todos.ADD and rappers.ADD in the same application, so you don’t have to type _TODO or _REMINDER every time you refer to an action for that feature.

Then save the following under /store/features/todos/actions.js:

import * as types from ‘./types.js’

export const addTodo = text => ({type: types.ADD, text})
export const deleteTodo = id => ({type: types.DELETE, id})
export const editTodo = (id, text) => ({type: types.EDIT, id, text})

This defines a few actions using the types of the string constants, presenting the arguments, and creating a payload for each. These don’t have to be entirely static, as they are functions. An example you could use is defining a runtime environment CUID for certain actions.

The most complicated piece of code, and where you’ll implement most of your business logic, is in reducers. These can take many forms, but the most commonly used configuration is a switch statement that handles each case depending on the type of action. Save this as reducer.js:

import * as types from ‘./types.js’

const initialState = [
{
text: ‘Hello World’,
id: 0
}
]

Export the todos of the default function (state = initialState, action) {
switch (action.type) {
types of cases.
come back [
…state,
{
id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
text: action.text
}
]

types of cases. REMOVE:
return state.filter (todo =>
todo.id! == action.id
)

types of cases.
return state.map (todo =>
todo.id === action.id? {… todo, text: action.text}: todo
)

default:
return state
}
}

The state is passed as an argument and each case returns a modified version of the state. In this example, ADD_TODO adds a new item to the report (with a new ID each time), DELETE_TODO removes all items with the given ID, and EDIT_TODO maps and replaces the item’s text with the given ID.

The initial state must also be defined and passed to the reduction function as the default value for the state variable. Of course, this does not define the entire state structure of Redux, only the state.todos section.

These three files are usually separated in more complex apps, but if you want you can also set them all in one file, just make sure you’re importing and exporting correctly.

Once this feature is complete, let’s connect it to Redux (and our app). In /store/root-reducer.js, import the todosReducer (and any other feature reducer from the / features / folder), then pass it to combineReducers (), forming a top-level root reducer that is passed to the store. This is where you will configure the root state, making sure to keep each feature on its own branch.

import {combineReducers} from ‘redux’;

import todosReducer from ‘./features/todos/reducer’;

const rootReducer = combineReducers ({
todos: todosReducer
})

export rootReducer by default

Using Redux in React

Of course, none of this helps if it’s not logged into React. To do this, you will need to wrap your entire application in a Provider component. This ensures that the necessary state and hooks are passed to every component in your application.

In App.js or index.js, wherever you have your root renderer, wrap your app in a and pass the store (imported from /store/index.js) to it as a prop:

import React from ‘react’;
import ReactDOM from ‘react-dom’;

// Configure Redux
import {Provider} from ‘react-redux’;
import store, {history} of ‘./store’;

ReactDOM.render (

, Document.getElementById (‘root’));

You are now free to use Redux in your components. The easiest way is to use function components and square brackets. For example, to dispatch an action, you would use the useDispatch () hook, which allows you to directly call actions eg. dispatch (todosActions.addTodo (text)).

The following container has an entry connected to the local React state, which is used to add a new task to the state each time a button is clicked:

import React, {useState} from ‘react’;

import ‘./Home.css’;

import {TodoList} from ‘components’
import {todosActions} from ‘store / features / todos’
import {useDispatch} from ‘react-redux’

function Home () {
const dispatch = useDispatch ();
const [text, setText] = UseState (“”);

function handleClick () {
dispatch (todosActions.addTodo (text));
Define the text (“”);
}

function handleChange (e: React.ChangeEvent) {
setText (e.target.value);
}

come back (

);
}

export the default home;

Then, when you want to use the data stored in the report, use the useSelector hook. It takes a function that selects part of the report to use in the application. In this case, it sets the publish variable to the current task list. This is then used to render a new todo element for each entry in state.todos.

import React from ‘react’;
import {useSelector} from ‘store’

import {Container, List, ListItem, Title} from ‘./styles’

function TodoList () {
const posts = useSelector (state => state.todos)

come back (

{posts.map (({id, title}) => (

{title}: {id}

))}

);
}

Export TodoList by default;

You can actually create custom selection functions to handle this for you, saved in the / features / folder just like actions and reducers.

Once you’ve got everything set up and figured out, you might want to take a look at the setup Redux Devtools, the middleware configuration like Redux Logger or responsive-connected-router, or installing a side effects model such as Redux Sagas.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.