How & when to use redux in react projects?
React is the most widely used javascript library according to StackOverflow’s 2021 survey. The ability to break down a larger application into multiple smaller & reusable components is one of the features that made react so popular as it greatly helps organize projects in a modular fashion.
But there’s one thing that may be an issue as projects start to scale. Imagine you are working on a big project. You will likely deconstruct your app into multiple child components and further divide them into smaller and more specific elements. While doing this, we usually use React’s state and props to share data and information across components. But this can quickly get complex and difficult to maintain if our project is big. This is where Redux comes in handy.
So, what is Redux?
In simple terms, Redux is just an application state management library. Redux attaches a central store to our root element and all other components can access the store for information. Redux is usually associated with React but it can be used with vanilla JavaScript and other frameworks or libraries.
In the redux world, there are four main concepts: Store, action, reducer, and dispatch.
The store is what tracks and stores our app data. Any component can reach out to the redux store via the connect() function for information.
An action is a JavaScript object that indicates the interaction taking place. Action has a mandatory type and optional payload properties.
A Reducer takes the current state and action object, do some operations, and return a new or current state. It is important that reducers rely only on the information inside their context.
Dispatch is a function that sends actions into the store. After the reducers run, the store is updated with a new state.

Let's see an example to better understand Redux. The following react-app uses redux. It is a simple app that shows a list of Tigray cities. It then displays the currently selected cities in a separate component when the show list button is clicked. The app has five components named Tigray, TigrayCities, SelectedCities, App, and index Components.
Try the app below and notice the console as you select cities.
The first step in integrating redux into our project is to install the required dependencies.
npm install redux react-redux @reduxjs/toolkit
Next, we import what we need to initialize our store with a reducer using the configureStore() function. The Provider element gets this store as a property in the root component.
import { Provider } from "react-redux";
import { configureStore } from "@reduxjs/toolkit";
import App from './App';// main store
const store = configureStore({reducer: reducers });
....//App component wrapped in Provider
root.render(<Provider store={store}>
<App />
<Provider /> );
This will set our app to use Redux. All components nested inside Provider will now have access to the store. The connect function from the react-redux library must be imported so as to connect our components with the store.
Connect function can take up to four arguments, but mapStateToProps and mapDispatchToProps are by far commonly used.
mapStateToProps — enables our component to get a real-time store state.
mapDispatchToProps — can be either used to dispatch actions to the store from a connected component.
import { connect } from "react-redux";const App = () => {
<Tigray />
}//use connect to get store dataexport defautlt connect()(App);
As shown above, connect function returns a new App component that is connected with the store. To access store data or to dispatch actions to the store, we can use mapStateToProps and mapDispatchToProps.
The Tigray component is also connected with the store. Look at the last line of the following code.
import React from "react";
import { connect } from "react-redux";
import addCityAction from "./actions/addCityAction";const Tigray = (props) => {
//props now contain storeState and addCityAction
const { city, description, imgUrl } = props; const onClick = (e) => props.addCityAction(e.target.value, description, imgUrl, e.target.checked);
};
const mapStateToProps = (state) => ({ storeState: state });
// wire mapStateToProps to the connect function
....
export default connect(mapStateToProps,{addCityAction})(Tigray);
Since the Tigray component is now connected to the store, it can access data or dispatch actions. In this case, the addCityAction is dispatched when a user toggles any of the checkboxes. The action is just a regular function that returns an object with a required type and optional payload fields. This is what the addCityAction looks like.
import * as ActionType from "./actionTypes";export default (city, description, img, checked) => {
return {
type: ActionType.ADD_CITY, //required
payload: {city, description, img, checked } //optional
};
};
What happens after this? Reducers take over from here. Reducers always run when any action is dispatched to the store and always take state and action parameters. They compare different actions and return a new state based on the type of action. In our case, the addCityReducer checks for the ADD_CITY action type and return a new and updated state. Changes are then instantly reflected in the store.
import * as ActionType from "../actions/actionTypes";
const addCityReducer = (state = {}, action) => {switch (action.type) { case ActionType.ADD_CITY: return { ...state, [action.payload.city]: { selected: action.payload.checked, description: action.payload.description, image: action.payload.img. }}; default:
//return the previous state for any other actions
return state; }};export default addCityReducer;
Because it is common to have multiple reducers, we usually combine our reducers. The addCityReducer is imported and passed into a redux function called combineReducers, which is then exported as ‘reducers’ by default.
import { combineReducers } from "redux";import addCityReducer from "./addCityReducer";export default combineReducers({ addCityReducer });
Next, reducers must be assigned to the store object. We have already imported and assigned reducers to our store at the root element with the following line of code.
// main store
const store = configureStore({reducer: reducers });
Now, selecting the city checkboxes will dispatch the addCityAction. And after this, addCityReducer will run and return a new state. The store now looks something like this.

Our store has saved some data. It is now easy to access this data from anywhere in our application. We just follow the same procedure. Connect the component, and avail the data inside the component using the mapStateToProps function. And more actions can also be dispatched from here. In our case, we have a modal component called SelectedCities. It simply reads the store data and returns the currently selected list of cities.
import React, { useState } from "react";import { Modal, Button } from "semantic-ui-react";import { connect } from "react-redux";const SelectedCities = (props) => {const selectedCitiesList = props.state.addCityReducer;const [open, setOpen] = useState(false);const renderCities = () => {
// keys of the selected list object
const selected = Object.keys(selectedCitiesList);const count = ("count", Object.values(selectedCitiesList).filter((a) => a.selected === true).length);if (count === 0) { return <div>You have not selected cities</div>;}return selected.map((element) => {
const city = selectedCitiesList[element];
// do not render if not selected
if (!city.selected) {
return null;
}
return (
<div key={element} className="item">
<img className="ui avatar image" src={city.image} alt {element} />
<div className="content">
<div className="header">{element}</div>
</div>
</div> );
});
};return (<React.Fragment>
<Modal onClose={() => setOpen(false)} onOpen={() => setOpen(true)}
open={open} trigger={ <Button className="floated-action-button">Show my list</Button> } >
<Modal.Header>Your selected cities</Modal.Header>
<Modal.Content>
<Modal.Description>
<div className="ui middle aligned selection list"> {renderCities()}
</div>
</Modal.Description>
</Modal.Content>
<Modal.Actions>
<Button color="black" onClick={() => setOpen(false)}>
Close
</Button>
</Modal.Actions>
</Modal>
</React.Fragment>
);
};
const MapStateToProps = (state) => ({ state: state });export default connect(MapStateToProps)(SelectedCities);
The full source code can be found at this link.
Conclusion
Redux is not recommended for simple applications like the one we did. It has an extra boilerplate that is just unnecessary for small apps. However, as projects start to grow, redux is very effective at managing information in one place.