Redux is one of the most confusing and probably difficult topics for someone who is trying to learn it from scratch. But why??
Are the number of boilerplates or the terminologies in Redux the reasons that turned you off from learning it?
You are searching for a Redux guide, you read some blogs/tutorials, and you watch some YouTube videos but things become more confusing and complicated when you find that different videos are telling some different approaches to build an application.
Store, Reducer, Action, Dispatch, Subscribe, and a lot of terminologies in Redux force you to think that why do we need to go with such a long process if things can be solved straightforwardly? You might be thinking that instead of simplifying things it’s just increasing the complexity of an application. You might have also come across some libraries such as Redux, React-Redux, Redux-thunk, Redux-saga, Redux-promise, Reselect, Recompose, and many more. Routing, authentication, server-side rendering, bundling….oh gosh!!! A lot of things are there to learn and it’s overwhelming for you. At some point, you start losing your mind…
Relax! You’re not alone who is experiencing this problem and struggling with learning Redux.
If you are searching for a single resource to understand all the basics, keeping everything else aside which is not important to learn then we are here to help you and in this blog, we will try our best to make you understand all the basic concepts of Redux without making you confused with a bunch of terminologies. Let’s start with that…
Firstly keep aside all the extra bit stuff and let’s just go with the Redux only. Right now we will only introduce the minimum things in Redux to learn now. There is no need to go in deep initially with some concepts like React-router, Redux-form, Reselect, Ajax, Webpack, Authentication, Testing, etc. Remember that you don’t run in one day, you first need to learn to walk.
Before you start learning Redux, make sure you know the basics of React.
What is Redux?
In the official documentation, Redux is defined as…
Redux is a predictable state container for JavaScript apps.
Well, at first these 9 words give you the feeling of 90 incomplete sentences where you don’t get anything. Well, the documentation has explanatory stuff when you start reading it. Take an example of the React application. Most of the time you define the state in your application at the top level of the component but have you noticed that when your application grows, all your state in a top-level component is no longer sufficient for you and now it’s difficult to manage all your state in the application? You may also have a lot of data changing in your application over time. Redux is introduced to solve all these problems.
State management is a big concern in large applications and Redux solves this problem. Some nice things you can do with Redux are logging, hot reloading, time travel, universal apps, recording, replay, etc.
Three Core Principles of Redux
We will keep it simple for you and let’s understand it first without using technical jargon. Let’s consider a real-life scenario of banks. You want to withdraw some cash from your bank account. You go to the bank branch with one intention/action in your mind i.e. WITHDRAW_MONEY. When you enter the bank you go straight to the counter with the Cashier to make your request. But….why do you need to talk to the cashier? Why you just don’t enter the bank vault to get your money?
You’re aware that there is a process that you need to follow to withdraw your money. When you talk to the cashier, he takes some time, checks some details, enters some commands, and hands over the cash to you. Let’s relate this example to Redux and understand some of its terminologies.
1. Consider the Redux Store as a bank vault and the State of your application is like money. The entire user interface of your application is a function of your state. Just like your money is safe in the bank vault, the state of your application is safe in the Redux Store. Now, this leads to the first principle of Redux…
Single source of truth: The state of your whole application is stored in an object tree within a single store.
Let’s simplify this statement more. Instead of littering your money everywhere in the bank, keep money in one vault. So in Redux, it is advisable to store the application state in a single object managed by the Redux store.
2. You visit the bank with action in your mind i.e WITHDRAW_MONEY. No one is going to give you money if you just roam around here and there. A similar thing happens in Redux. If you want to update the state of your Redux (like you do with setState in React) you need to let Redux know about your action. Just like you follow a process to withdraw money from your bank, Redux also follows a process to change/update the state of your application. This leads to the second principle of Redux.
State is read-only
Note: The only way to change the state is to emit an action an object describing what happened.
The meaning of the above statement is quite simple. In Redux your action WITHDRAW_MONEY will be represented by an object and it looks something like below…
Javascript
{ type: "WITHDRAW_MONEY" , amount: "$10,000" } |
The above object is an action in the Redux application that has a type field describing the action you want to perform. So whenever you need to change/update the state of your Redux application, you need to dispatch an action.
3. Consider your cashier in the bank as a Reducer in your Redux application. To WITHDRAW_MONEY from your bank vault, you need to convey your intention/action to the cashier first. Now the cashier will follow some process and it will communicate to the bank vault that holds all the bank’s money. A similar thing happens in Redux. To update the state of your application you need to convey your action to the reducer. Now the reducer will take your action, it will perform its job and it will ensure that you get your money. Your Reducer always returns your new state. Sending off the action to the reducer is called dispatching an action. This leads to the last or third principle of Redux.
To specify how the state tree is transformed by actions, you write pure reducers.
We will understand the pure Reducer later in this blog. Hope we have explained well about three main terminologies of Redux: The Store, The Reducer, and Action.
Let’s build The First Simple Redux Application
With a real-life example, we understood the principles and some common terminologies of Redux but how to introduce all these things in an application? To deepen your fundamental concepts in Redux let’s take an example of a simple React application, and we will refactor the app to introduce Redux in it.
If you’re familiar with React then you won’t have a problem in understanding the structure of the above React application. You can create this application using the create-react-app command. In your application, the main App component is importing a <HelloTech /> component and renders the information in its body. The <HelloTech /> component takes in a tech prop, and this prop displays different technologies to the user. For example <HelloTech tech=”Redux” /> will display the below result to the user….
Below is the code for App component…
src/App.js
Javascript
import React, { Component } from "react" ; import HelloTech from "./HelloTech" ; class App extends Component { state = { tech : "React" } render() { return <HelloTech tech={ this .state.tech}/> } } export default App; |
We are passing the tech as a prop into the HelloTech component as shown below:
<HelloTech tech={this.state.tech}/>
For now, forget about the HelloTech component implementation. It’s just taking the tech prop and using some CSS for styling purposes. The main concern here is to refactor the App component and use Redux in it. Redux is the state manager for our application, so we need to take away the state object, and we want it to be managed by Redux. Remember the example of the bank vault, it keeps all the money. In a similar way, the Redux store manages the overall application state and it keeps the application state object. So we need to first remove the current state object from App.js and we need to install Redux by running npm install –save redux from the command-line interface.
Javascript
import React, { Component } from "react" ; import HelloTech from "./HelloTech" ; class App extends Component { // the state object has been removed. render() { return <HelloTech tech={ this .state.tech}/> } } export default App; |
Create Redux Store
In the case of the bank vault, some engineers might be hired to create a secure money-keeping facility. Similarly, here in Redux, Some APIs are provided by the Redux library to create the Store facility. Below is the code to create a Store in Redux…
Javascript
import { createStore } from "redux" ; //an import from the redux library const store = createStore(); |
We have imported the createStore factory function from Redux, and then we have invoked the createStore() function to create the store.
Define Store and Reducer Relationship
When you visit the bank to withdraw your money and let your action known to the cashier you do not get the money instantly. Firstly the cashier checks your account that if you have enough money to perform the transaction or not. It communicates with the bank vault for this information. In a nutshell, the Cashier and Vault are always in sync. A similar thing happens in Redux. The Store (Bank Vault) and the Reducer (Cashier) communicate with each other, and they are always in sync. But how to write this logic in our code??? We pass the reducer as an argument in createStore() function and below is the complete code for App.js
App.js
Javascript
import React, { Component } from "react" ; import HelloTech from "./HelloTech" ; import { createStore } from "redux" ; const store = createStore (reducer); class App extends Component { render() { return <HelloTech tech={ this .state.tech}/> } } export default App; |
The Reducer
If you hear the word Reducer it sounds like it is a reducing function that performs a ‘Reduce’ kind of job. Well, In JavaScript, you already use Reducer and that is an Array.reduce() method (you might be aware of this method). This method takes two values accumulator and currentValue. Take a look at the example given below…
Javascript
let arr = [1, 2, 3, 4, 5] let sum = arr.reduce((x,y) => x + y) console.log(sum) // 15 |
Here x is the accumulator and y is the currentValue. A similar thing happens in Redux. Reducer is a function in Redux that takes two parameters. One is the STATE of the app and the other is ACTION.
Now we need to include the Reducer in our app which we haven’t done yet. So create a directory reducer and create a file index.js in it. The path for the reducer function will be src/reducers/index.js. Right now we will just pass Store as an argument in Reducer, and we will just export a simple function. The code looks like below…
Javascript
export default (state) => { } |
In Array.reduce() we returned the sum of the accumulator and current value. If you take the example of our bank scenario then after withdrawal, the money in your bank vault is no longer the same. It will be updated and again, the Cashier and Vault will remain in sync with the balance left in your account. Just like the cashier the reducer always returns the new state of your application. We will talk about changing/updating the state later in this blog. Right now consider a case in which you visit the bank and you didn’t perform any action so the bank balance remains the same. In Redux if you won’t perform any action and you don’t pass the Action as an argument in Reducer then the state will remain the same and the reducer will return the same state as a new state. At this point keep a new state being returned as the same state passed in.
Javascript
export default (state) => { return state } |
Second Argument for createStore
When you created an account in your bank, you might have deposited some amount in your account, and if you ask the cashier for your bank balance they’ll look it up and tell it to you. In the same way, when you create a Redux Store you do a similar kind of initial deposit which is known as initialState. We will represent this initialState as a second argument passed into the createStore.
Javascript
const store = createStore(reducer, initialState); |
So the initialState is like an initial deposit in your Store (bank vault) and if you don’t perform any action this initialState will be returned as the state of the application. Below is the updated code in App.js with initialState in our application.
App.js
Javascript
import React, { Component } from "react" ; import HelloTech from "./HelloTech" ; import reducer from "./reducers" ; import { createStore } from "redux" ; const initialState = { tech: "React " }; const store = createStore(reducer, initialState); class App extends Component { render() { return <HelloTech tech={ this .state.tech}/> } } export default App; |
At this point, your application will throw an error if you run it because the tech prop still reads, this.state.tech. We have removed the state object from our application so it will be undefined. Right now the state is entirely managed by Store so we need to replace this line with the getState() method which is available when you create a store with createStore() method. If we call the getState method on the created store, we will get the current state of our application. The INITIAL STATE passed into the createStore() is represented by the object {tech: React}. So in our case store.getState() will return this object. Below is the updated code for App.js
App.js
Javascript
import React, { Component } from "react" ; import HelloTech from "./HelloTech" ; import { createStore } from "redux" ; const initialState = { tech: "React " }; const store = createStore(reducer, initialState); class App extends Component { render() { return <HelloTech tech={store.getState().tech}/> } } |
Congratulations!!! We just have refactored a simple React application to use Redux. Now the state is managed by the Redux. Now let’s move to the next topic which is Actions in Redux.
Redux Action
In our bank case scenario, your intention/action was WITHDRAW_MONEY. You have to let your action known to the cashier and the cashier will be responsible for updating the money in your account and handover it over to you. The same thing happens in the Redux reducer. In a pure React application, we use the setState method to update the state in our application but here we need to let our action known to the Reducer to update the state in your application. But how??
By dispatching and action! And how to do that???
We just need to describe the action by a plain JavaScript object and this object must have a type field to describe the intent of the action. The code will look like something below…
Javascript
{ type: "withdraw_money" } |
You won’t get the money if you only tell your action to withdraw money from the cashier. You also need to mention the amount. Many times in our application we also need to add some additional information for full details. So we will add one more information amount in our code and it looks something like the below…
Javascript
{ type: "withdraw_money" , amount: "$3000" } |
We can include some more information but for now, it’s sufficient, and ignore the other details. It’s really up to you how you structure your action but a standard/common approach in Redux is using the payload field. We put all the required data/information in the payload object that describes the action and it looks something like the below…
Javascript
{ type: "withdraw_money" , payload: { amount: "$3000" } } |
Handling Responses to Actions in the Reducer
We have discussed that Reducer takes two arguments in order to update the application. One is the state and the other is action. So a simple Reducer looks something like below…
Javascript
function reducer(state, action) { // return new state } |
Now to handle the action passed into the Reducer we typically use switch statements in Redux which is nothing but basically, an if/else statement.
Javascript
function reducer (state, action) { switch (action.type) { case "withdraw_money" : //do something break ; case "deposit-money" : //do something break ; default : return state; } } |
In the above example, we have taken two actions. One is withdraw_money and the other one is deposit_money. In your Redux application based on the requirement, you can define as many actions as you want but every action flows through the reducer and that’s what we have done in the above code. In the above code, both actions pass through the same reducer and the reducer differentiates each of them by switching over the action.type. This is how each action can be handled separately in Reducer without any inconvenience. Further in the above code, we just need to define the do something part to return a new state.
Examining the Actions in the Application
Let’s go back to the previous example of HelloTech and understand how to update the state of the application. Look at the image given below. There are three buttons and our aim is to change the text/technology whenever a specific button is clicked. To do, so we need to dispatch an action, and the state of the application needs to be updated.
For example, if the button React-redux is clicked it should look something like the below…
From the above image, it is clear that we need to describe three actions…
For the React button:
Javascript
{ type: "SET_TECHNOLOGY" , text: "React" } |
For the React-Redux button:
Javascript
{ type: "SET_TECHNOLOGY" , text: "React-redux" } |
For the Elm button:
Javascript
{ type: "SET_TECHNOLOGY" , text: "Elm" } |
All three buttons do the same thing. That is the reason we have defined all three actions with the same type of field. Treat them like customers in a bank with the same intent/action of depositing money (type) but different amounts (text).
Action Creators
If you look at the code we have written for creating actions you’ll notice that a few things are repeated in the code. For example, the same type of field is written multiple times which is not good as per the DRY principle in programming. To keep our code DRY we need to look at the new term Action Creators. What are those….??? Let’s discuss that.
Actions creators are just simple functions that help you to create actions keeping your code DRY and it returns action objects. Below is the code…
Javascript
export function setTechnology (text) { return { type: "SET_TECHNOLOGY" , tech: text } } |
In the above function, we just need to call the function setTechnology and we will get the action back. There is no need to duplicate the code everywhere. We can simplify this more, and we can write the same above code using the ES6 feature.
Javascript
const setTechnology = text => ({ type: "SET_TECHNOLOGY" , text }); |
Let’s Bring Everything Together
In this section, we will talk about folder structure, and we will see how to put everything in specific folders/files to keep things organized. If we talk about our bank case scenario then you will notice that things are organized in their own place. For example, the cashier sits in their own cubicle/office and the vault is safe in separate secure rooms. We will do a similar thing in Redux. It’s totally up to you how you want to structure your project but a common approach in Redux is to create a separate folder/directory for the major components such as reducer, actions, and store.
Create three different folders reducers, stores, and actions. In each of the folders create an index.js file that will be the entry point for each of the Redux components. Now refactor the application we have built before and put everything in its own place.
store/index.js
Javascript
import { createStore } from "redux" ; import reducer from "../reducers" ; const initialState = { tech: "React " }; export const store = createStore(reducer, initialState); |
Whenever we need to Store anywhere in our app we can import the above file mentioning the path import store from “./store”;
Now the file App.js will have a slight difference in its code.
Javascript
import React, { Component } from "react" ; import HelloTech from "./HelloTech" ; import ButtonGroup from "./ButtonGroup" ; import { store } from "./store" ; class App extends Component { render() { return [ <HelloTech key={1} tech={store.getState().tech} />, <ButtonGroup key={2} technologies={[ "React" , "Elm" , "React-redux" ]} /> ]; } } export default App; |
In the above code line, 4 has been changed according to the path of the Store. We have also imported a component ButtonGroup which is basically responsible for rendering the three buttons. <ButtonGroup /> component takes an array of technologies and spits out buttons.
Another thing you need to notice is that the App component returns an array. In React 16 you don’t need to use <div> to wrap the JSX element. like we were doing earlier in React. We can use an array and pass the key prop to each element in the array to perform the same job.
Let’s move to the ButtonGroup component. It’s a stateless component that takes in an array of technologies which is denoted by technologies.
ButtonGroup.js
Javascript
import React from "react" ; const ButtonGroup = ({ technologies }) => ( <div> {technologies.map((tech, i) => ( <button data-tech={tech} key={`btn-${i}`} className= "hello-btn" > {tech} </button> ))} </div> ); export default ButtonGroup; |
In the above code, the buttons array passed in is [“React”, “Elm”, “React-redux”]. We need to loop over this array using the map to render each of the techs in <button></button>. Generated buttons have a few attributes as well such as key and data tech. A completely rendered button will look like this:
Javascript
<button data-tech= "React-redux" key= "btn-1" className= "hello-btn" > React </button> |
This will render all the buttons but nothing will happen if you click the buttons. We need to use the onClick handler within the render function.
Javascript
<div> {technologies.map((tech, i) => ( <button data-tech={tech} key={`btn-${i}`} className= "hello-btn" onClick={dispatchBtnAction} > {tech} </button> ))} </div> |
Now, remember the code to dispatch the action for individual tech React, React-redux, and Elm.
Javascript
{ type: "SET_TECHNOLOGY" , tech: "React" } |
and React-redux will be like…
Javascript
{ type: "SET_TECHNOLOGY" , tech: "React-redux" } |
So now we need to write the code for dispacthBtnAction function to dispatch the action whenever we click any button. Below is the code…
Javascript
function dispatchBtnAction(e) { const tech = e.target.dataset.tech; store.dispatch(setTechnology(tech)); } |
The above code doesn’t make sense to you….right??? Here is the explanation…
- e.target.dataset.tech gets the data attribute set on the button data-tech. Hence, the tech will hold the value of the text.
- store.dispatch() defines how you dispatch an action in Redux.
- setTechnology() is the action creator we wrote about earlier.
Now, remember the same bank case scenario. For your action WITHDRAW_MONEY, you interact with the cashier…yeah??? This means if you want your money, your action needs to be passed through the cashier. The same thing is happening in the above code. When you are dispatching an action, it passes through the Reducer (cashier).
Returning a New State
Till now the cashier in the bank did nothing with WITHDRAW_MONEY action. We expect the cashier to update the money in the bank vault and hand over the money to you. In our Redux app, we also want our Reducer to return a new state which should have the action text in there.
What exactly we mean is that if the current state is { tech: “React”} then with a new action given below…
Javascript
{ type: "SET_TECHNOLOGY" , text: "React-Redux" } |
We expect the new state to be {tech: “React-Redux”}
We have discussed earlier that to handle different action types we can use switch statements in our Reducer code with different actions in mind. In our bank case scenario, the cashier will respond according to the intent/action given by the customer. These actions could be WITHDRAW_MONEY, DEPOSIT_MONEY, or just SAY_HELLO. Similarly, the reducer will respond based on your intent. Right now we just have one case SET_TECHNOLOGY so the code will look like something below…(The cashier that actually gives us money)
Javascript
export default (state, action) => { switch (action.type) { case "SET_TECHNOLOGY" : return { ...state, tech: action.text }; default : return state; } }; |
Here notice that we are returning a new copy of the state, a new object using the ES6 spread operator …state. We are not supposed to mutate the state received in our Reducer. Technically you should not write the code something like below…
Javascript
export default (state, action) => { switch (action.type) { case "SET_TECHNOLOGY" : state.tech = action.text; return state; default : return state; } }; |
Also, Reducer should be a pure function in our code with no side effects — No API calls or updating a value outside the scope of the function. Now the cashier is responding to your action and giving you the money you requested for. But again if you click the button you can not see the text updated on your screen…. let’s discuss it with a new term subscribe.
Subscribing to Store Updates
Well, you have received your money from the cashier but what about some sort of personal receipt or notification/alert via email/mobile? Most likely you receive a notification regarding your transaction and the balance left in your account. You receive that because you have subscribed to receive transaction notifications from the bank either by email/text. A similar thing happens in Redux. To receive an update after the successful action is initiated, you need to subscribe to them. Now the question is…how?
Redux provides subscribe method to do this job. We need to use the store.subscribe() function, and we need to pass the argument in it. Whenever there’s a state update this argument will be invoked. We need to keep in mind that the argument passed into it should be a function.
Once the state is updated we expect our application to re-render the new state values. The entire application renders in the main index.js file of our application. If you open this file you will find the code given below…
Javascript
ReactDOM.render(<App />, document.getElementById( "root" ) |
The above function can be also written using the ES6 feature.
Javascript
const render = () => ReactDOM.render(<App />, document.getElementById( "root" )); render(); |
In the above code, we just represented the app into a function, and then we invoked the function to render the app. Now we can pass the above-refactored render logic into our store.subscribe() function.
Javascript
store.subscribe(render); |
Now the <App /> will be re-rendered with a new state value whenever there’s a successful state update to the store. Below is the <App/> component.
Javascript
class App extends Component { render() { return [ <HelloTech key={1} tech={store.getState().tech} />, <ButtonGroup key={2} technologies={[ "React" , "Elm" , "React-redux" ]} /> ]; } } |
store.getState() in line 4 will fetch the updated state whenever a re-render occurs. Now the app will work as you expect it to work and you will see the updated technology whenever you will click a specific button.
Congratulations!!! We are successfully dispatching an action, receiving money from the Cashier, and then subscribing to receive notifications.
That’s it for now…
Conclusion
We have discussed all the main terminology of Redux, and we have tried our best to explain each one of them in the simplest way. But the journey of learning Redux doesn’t end here. We suggest you practice some more exercises on Redux and build some more complex projects. Also, don’t get afraid of so many libraries available in Redux. Each library has its own specific job that you will understand slowly and gradually.
We hope Redux won’t scare you anymore!!!