Photo by Umberto on Unsplash

Redux with React for Complete Beginners

When I was beginning with react it was a little bit hard to get around with redux state management. I failed interviews because I couldn’t explain well how this works. And so later I learnt this by building apps and trying tutorials on redux. It took some time to actually get a grasp on it. But when I understood the concepts behind it was never that hard.

Here, I wanted to explain the concepts and setups as easy as possible, so a beginner with react knowledge can understand it.

Why state management is Important

When you build your application which has many components, it becomes difficult to subscribe to state changes. You might need to keep lifting the state up to the parent component then passing it down the tree as props for other components to use it. Which is not ideal for large applications. Code becomes less readable and a lot of repetition occurs. That’s why redux comes to rescue. There are alternatives to redux like mobx, rxjs(usually used with angular)

So let's start here with the diagram below

Application without redux

Here is an app diagram without redux. Consider all the circles as components. A worst-case would be a component down at the tree level initiate an update to a state which in turn initiate change in the parent component and from parent component other components may need to subscribe to this changes. But in practice, this is quite overwhelming. The state will be needed to be lifted to the top parent level where the changes can be drilled into other components.

With state management library Redux you can easily solve this problem

Application with Redux

Here a change is initiated and the store gets the update with the actions you dispatch to make the change. In redux, you dispatch actions to update the state. You should not directly mutate the state otherwise the components will not subscribe to the changes and thus re-rendering will not happen.

Redux Data Flow

It's important to understand the basic structure of how redux works. Redux follows the unidirectional data flow. It means that your application data will follow in a one-way binding data flow. It reduces the complexity of code on when to update the state with new changes.

Action is created when a user interacts with the view, then this action is dispatched to root reducer function. Now the root-reducer function is called with the current state and dispatched action.

The reducer may divide the action among its child reducers and a new state returns after executing the actions. Now the store notifies the view about the new changes. The view now can retrieve the changes and re-render again.

Let’s create a basic app with react and redux

npx create-react-app react-redux-tutorial

Let’s move into your created folder now

cd react-redux-tutorial

Now let’s install react-redux and redux. If you are using npm its npm install command to install the dependencies

yarn add redux react-redux

or

npm install redux react-redux

Redux is the main library that helps to create a store, make actions and listen to state updates. Basically utilizing Redux with any UI layer requires the equivalent predictable arrangement of steps:

  1. Make a Redux store
  2. Buy into refreshes
  3. Inside the membership callback:
    i. Get the present store state
    ii. Concentrate the information required by this bit of UI
    iii. Update the UI with the information
  4. Render the UI with starting state
  5. React to UI contributions by dispatching Redux actions

That's just the concept behind adding Redux library with installation, now to bind redux to react you can do it by hand-coding, but it's not ideal for performance and to keep it DRY and readable. So instead you have the right tool react-redux which help us to do all the redux bindings with react.

Ok now lets set up redux in working mode with your app.

Let's add a folder name redux inside the src folder of your project and create two files one named as root-reducer.js and the other as store.js

Setup for root-reducer.js as follows:

import { combineReducers } from  "redux";
const initialState = {
books: [],
};
function bookReducer(state = initialState, action) {
return state;
}
const rootReducer = combineReducers({
bookReducer,
});
export default rootReducer;

You are creating an Initial state here with a field book as a sample data and also a function bookReducer to help with updating the state of book in your initial state. Later you can move this Initial state of the book and bookReducer function into a separate folder and just import them to rootReducer that’s usually done when you are having a lot of state data and you wanted to classify them separately

And you could simply write all the reducers in a single pure function, buts it's not ideal for debugging purpose when you plan for a large application. What you do is import combineReducers which basically helps us to combine several reducer functions together.

Setup for store.js

import { createStore } from  "redux";
import rootReducer from "./root-reducer";
export const store = createStore(rootReducer);

Here you are actually creating the store and connecting your reducer to the store so that your app can successfully subscribe to the changes in the state.
The createStore just creates a Redux store that holds the complete state tree of your app. There should only be a single store in your app.

Then you are just importing your root reducer and connecting it to the createStore function, this createStore( ) function returns the store for your app which you are assigning to a constant variable store
Let’s connect the store to your app. Right now you only created some files you are not doing anything to make your app read the state. So let’s go to index.js that is your root component.
At index.js you import provider fromreact-redux. The <Provider /> makes the Redux store available to any nested components that are wrapped with the connect function. The connect function is just a function that takes the component as a parameter and connects the redux store to it. That's how the redux works when the state changes the component gets rerendered.

Setup at index.js

import  React  from  "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { Provider } from "react-redux";
import { store } from "./redux/store";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>,
document.getElementById("root")
);
serviceWorker.unregister();

Now if you wanted to change the state of your app, the only way to do is by dispatching actions.
Ok now let's create actions! Let's create two more files in the redux folder and name it as types.js and actions.js
Note: Here you are actually creating two files in the same folder, but when your app is large its best to create new folders with the appropriate name and add the related actions, types and reducers files to it, so that classification is much easier. You will thank yourself for doing this later.

Setup for types.js

export  const  actionTypes  = {
ADD_BOOK: "ADD_BOOK",
};

Types are nothing but signalling the type of actions you are doing. Actually its perfectly fine to go without this, by writing the action names directly to your actions and reducers. But this is not much recommended since you can miss type names, so the state updation doesn't happen since this won't throw any errors, for us to debug properly.

Setup for actions.js

import { actionTypes } from  "./types";
export const addBooksToState = (data) => ({
type: actionTypes.ADD_BOOK,
payload: data,
});

Now you need to make the reducer aware of this action type so you can modify your root-reducer.js as follows

import { combineReducers } from  "redux";
import { actionTypes } from "./types";
const initialState = {
books: [],
};
function bookReducer(state = initialState, action) {
switch (action.type) {
case actionTypes.ADD_BOOK:
return {
...state,
books: [...state.books, action.payload],
};
default:
break;
}
return state;
}
const rootReducer = combineReducers({
bookReducer,//you can add more reducers here
});
export default rootReducer;

Now the important concepts here are map state to props and map dispatch to props you can declare both of them as functions. Map state to props is just simply as what it says. It allows your component to know what states are you going to subscribe and use.
Similarly, the dispatch lets the component to update the state by actions from your actions file.
for example, this is how you define it with the redux store and actions you created

const  mapStateToProps  = (state) => ({// you can do destructuring
//here
books:state.bookReducer.books
})
const mapDispatchToProps =(dispatch)=>( {
addBooksToState:(book)=>dispatch(addBooksToState(book))
// you have to import the actions
})

Now you have basically completed the setup of your redux store along with actions.
You can find the code up to now, here

Optional Section: A fully functional app with the redux setup

Let's explore more with your complete app.
So in the src folder lets create another folder components and inside let's create a file with name as books.jsx

import  React  from  "react";
import { connect } from "react-redux";
const Books = ({ books }) => {
return (
<div>
<h1>Books List</h1>
<ul>
{books.map((book, index) => (
<li key={index}>{book}</li>
))}
</ul>
</div>
);
};
const mapStateToProps = (state) => ({
books: state.bookReducer.books,
});
export default connect(mapStateToProps)(Books);

ok now that's done let's create another file books-input.jsx in the same folder. Here you add books to the store

import  React  from  "react";
import { connect } from "react-redux";
import { addBooksToState } from "../redux/actions";
const BooksInput = ({ addBooksToState }) => {
const handleKeyPress = (e) => {
if (e.key === "Enter") {
addBooksToState(e.target.value);
document.getElementById("book-input").value = "";
}
};
return (
<div>
<h2>Add your books here</h2>
<input id="book-input"
type="text"
onKeyUp={handleKeyPress} />
</div>
);
};
const mapDispatchToProps = (dispatch) => ({
addBooksToState: (book) => dispatch(addBooksToState(book)),
});
export default connect(null, mapDispatchToProps)(BooksInput);

Now let's plug these components to your App.js.

import  React  from  "react";
import "./App.css";
import Books from "./components/books";
import BooksInput from "./components/books-input";
function App() {
return (
<div className="App">
<Books />
<BooksInput />
</div>
);
}
export default App;

Now your application is running with redux state management. You can now add more features. For example, to avoid adding new books if its already in the state, you can do the following in your root-reducer.js
Replace the corresponding snippet with this

case  actionTypes.ADD_BOOK:
if (!state.books.includes(action.payload)) {
return {
...state,
books: [...state.books, action.payload],
};
}
return state;

You can find the final app here

And the rest is up to you to go and try out. You can do a lot to improve the app. Since it's beyond the scope of my article.

  • Add an edit button to edit the added books
  • Delete a book
  • Improve design
  • Configure a database with Express and MongoDB (maybe PostgreSQL)

Please let me know if this tutorial helped you.

You can follow me at Twitter and Linkedin

Software Developer. Love to create beautiful things.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store