Demystifying the Redux Flow

Sisi Remick
5 min readSep 5, 2023

--

Action, reducer, dispatch, and state. All of these elements are part of Redux’s core concepts… and most of them are new to many React developers. To define the word, Redux is “a powerful state management library commonly used in React applications. It provides a predictable and efficient way to manage application state” (thank you ChatGPT). If you’re a React developer looking to level up your state management for your ever growing apps, follow along. In this post I’ll show the basics of how these core Redux elements work together to help you maintain state more efficiently.

Reading about Redux for the first time like…

Actions: The Blueprint

Actions describe what take place in your app. They are expressed as plain old JavaScript objects with two attributes: type and payload. Their job is to 1) describe the type of action that needs to take place, and 2) provide any data required to perform that action.

In the example below, I provided a code snippet pulled from an Instagram-like app that I am currently working on. Here’s what an “add comment” action might look like:

export const addComment = (comment) => {
return {
type: "comments/add",
payload: comment,
};
};

Actions can be imported into components throughout your application, and dispatched to the Redux store to request a change in application state based on user interaction.

Dispatch: Make a Request

Dispatching an action is the process of sending it to the Redux store. To do so, you’ll need to install the react-redux library, and then import useDispatch into the component you’ll be making the request from. To dispatch an action, such as submitting a new comment via a form, you just need to pass the action you already defined as an argument to the dispatch method.

In the code snippet below, you can see how after successfully submitting a new comment to my database, I need to then update the frontend of my app’s state as well. To do this, I imported my action, addComment, and the dispatch method to send the newComment data to the Redux store.

//rest of component code
import { addComment } from './commentsSlice';
import { useDispatch } from "react-redux";

function NewCommentForm () {
//rest of component code
const dispatch = useDispatch();

function onSubmitComment(e){
e.preventDefault()

const newComment = {
content: newContent,
post_id: post_id
}

fetch(`/posts/${post_id}/comments`, {
method: 'POST',
headers: {"Content-Type": "application/json"},
body: JSON.stringify(
newComment
)
})
.then(res => {
if(res.ok){
res.json().then((newComment) => {
dispatch(addComment(newComment))
//^^see dispatching an action here!
setNewContent('')
navigate(`/users/${id}/posts/${post_id}`)})
} else {
res.json().then((message) => {
const errorLis = message.errors.map(error => <li key={error}>{error}</li>)
setErrorsList(errorLis)
})
}
})
}

//rest of component code

Behind the scenes, Redux takes the dispatched action and sends it to the relevant reducer.

Reducers: Handling the Action

Reducers are functions responsible for taking in the application’s current state value and a dispatched action, and then updating state accordingly.

Here’s an example of a commentsReducer for in my Instagram-like app:

const initialState = {
entities: []
};

export default function commentsReducer(state = initialState, action) {
switch (action.type) {
case "comments/add":
return {
...state,
entities: [action.payload]};

default:
return state;
}
}

You can see that in this file, I have set an empty object to the value of my app’s initial state. I then set that value as my default for the state argument taken into my reducer function. When the addComment action is dispatched from elsewhere in my app, it is sent here by Redux, passed into the commentsReducer as an argument, and my function will return the relevant updated state value depending upon the action’s type property and value of state at that moment in time.

State: The Application’s Snapshot

The state in Redux represents the entire state tree of your application, and it is saved in something typically referred to as the store. As you dispatch actions and reducers process them, the state gets updated accordingly in your store.

To set up your store, I’d recommend creating a store.js file in your src file like so:

// app/client/src/store.js

import { applyMiddleware } from "redux";
import { configureStore } from "@reduxjs/toolkit";
import thunkMiddleware from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
import rootReducer from "./reducers.js";

const composedEnhancer = composeWithDevTools(applyMiddleware(thunkMiddleware));

const store = configureStore({
reducer: rootReducer
}, composedEnhancer);

export default store
// app/client/src/reducers.js

import { combineReducers } from "redux";
import usersReducer from "./features/users/usersSlice";
import likesReducer from "./features/likes/likesSlice";
import postsReducer from "./features/posts/postsSlice";
import commentsReducer from "./features/comments/commentsSlice";
import authReducer from "./features/auth/authReducer";

const rootReducer = combineReducers({
comments: commentsReducer,
{/* other reducers here */}
});

export default rootReducer;
// app/client/src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import App from './App';
import store from "./store.js"

ReactDOM.render(
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>,
document.getElementById("root")
);
// app/client/src/App.js
import React, { useEffect } from "react";
import { useDispatch } from 'react-redux';
import { loadUserFromStorage } from './features/auth/authActions';

function App() {
const dispatch = useDispatch();

useEffect(() => {
dispatch(loadUserFromStorage());
}, [dispatch]);

return (
<div className="App">
{/* rest of component code */}
</div>
);
}

export default App;

I have provided snapshots of some other relevant file structures you’ll need to make the store work correctly in the example above, but I will not be going into detail on these as that is beyond the scope of this blog post.

What you need to take away after store setup is that components in your application can access this global state to display data and respond to changes.

Bringing It All Together

The Redux flow starts with actions, which are dispatched to the store. Reducers listen for these actions and update the state based on how you define their functions. Components in your application can access this data and your App can be configured to re-render on dispatching changes. This predictable flow ensures that your application remains consistent and easy to debug.

In this post, we’ve scratched the surface of Redux’s core concepts. There is certainly a lot to learn, but understanding the basics of the Redux flow is crucial for building maintainable and scalable applications.

--

--

Sisi Remick

Certified in Software Engineering from Flatiron School, a renowned coding bootcamp with an intensive and cutting-edge curriculum.