Redux can be confusing for beginner React developers to understand. There are a lot of concepts you need to know to use it properly, like reducers, actions, store, pure functions, immutability, and much more.
But every React developer should know the basics of how to work with Redux since industry projects often use Redux to manage larger projects.
So in this article, we'll explore the basics of Redux and how to use it.
Here's a preview of the app which we'll be building in the Mastering Redux course.It's a great project you can add to your portfolio and resume.
Today is the last day to get the course at discounted price.
Note that, in this app, I have used INR as the currency for displaying the prices but you can easily change it to USD or AUD or any other currency with a single configuration change in the app.
What Is Redux?
Redux is a state management library that helps to better manage state in the application.
Redux library is not specific to React. It's a library that you can use in any other library or framework like Angular, Vue and even vanilla JavaScript.
But Redux is mostly used when working with React.
Redux provides a single store that we can use to manage a large amount of data.
How to Get Started with Redux
Let's create a new React project to learn Redux basics.
Execute the following command in the terminal/command prompt to create a new React project using create-react-app:
npx create-react-app redux-demo
npx
in this case allows us to use thecreate-react-app
npm package to create a new React project without installing it on our local machine.
Once the project is created delete all the files from the src
folder and create a new file index.js
inside the src
folder.
Now open the terminal again and execute the following command from the redux-demo
folder:
npm install redux@4.1.0
The above command will install the redux library with version 4.1.0
to use in our project which is the latest version at the time of writing this article.
How to Create the Redux Store
In Redux, the store is used to manage and track the changing data in the application.
To create a store we need to import the createStore
function like this:
import { createStore } from 'redux';
The createStore
function accepts three arguments:
- the first argument is a function that is normally known as a reducer (required)
- the second argument is the initial value of the state (optional)
- the third argument is an enhancer where we can pass middleware If any (optional)
You will see how to create your own middleware as well as use the already available middleware libraries in the course.
Take a look at the below code:
import { createStore } from 'redux';
const reducer = (state, action) => {
console.log('reducer called');
return state;
};
const store = createStore(reducer, 0);
Here, we've first defined a reducer function using ES6 arrow function syntax. You can use the normal function instead of the arrow function If you want.
Inside the reducer function, we're logging some text to the console and then returning the value of the state from the function.
Then we pass that reducer function to the createStore
function as the first argument and 0
as the initial value of the state as the second argument.
The createStore
function returns a store that we can use to manage the application data.
The reducer function receives state and action as the parameters.
The initial value of the state which we passed as 0
for the createStore
function is automatically passed as the value of the state
parameter.
But it's a much more common practice to initialize the state inside the reducer itself rather than passing it as a second argument to the createStore
function like this:
import { createStore } from 'redux';
const reducer = (state = 0, action) => {
console.log('reducer called');
return state;
};
const store = createStore(reducer);
Here, we're using ES6 default parameter syntax for initializing the state parameter to value 0
.
Once the store is created, we can use the subscribe
method provided by the store to subscribe to the changes in the store as shown below:
store.subscribe(() => {
console.log('current state', store.getState());
});
Here, using the subscribe
function, we're registering a callback function that will be called once the store is changed.
And inside the callback function, we're calling the store.getState
method to get the current value of the state.
Now, open the src/index.js
file and add the following contents inside it:
import { createStore } from 'redux';
const reducer = (state = 0, action) => {
console.log('reducer called');
return state;
};
const store = createStore(reducer);
store.subscribe(() => {
console.log('current state', store.getState());
});
Now, If you run the application by executing the npm start
command from the terminal and access http://localhost:3000/, you will see the reducer called
message printed in the console.
This is because the reducer gets called immediately once we pass it to the createStore
function.
How to Change the Store
Now, we're done with creating the store. But the store is not much of use right now. Because the store is connected using the reducer function but we have not added any code inside the reducer to manage the store. So let's do that.
The only way to change the store is by dispatching actions.
An action is an object sent to the store like this:
store.dispatch({
type: 'INCREMENT'
})
Here, we're calling the dispatch function available on the store
to send an action with the type INCREMENT
to the store.
The dispatch function takes an object as a parameter which is known as action.
The action must have a type
property as shown above. If you don't pass the type
property then you will get an error.
It's a common practice and recommended to specify the
type
value in uppercase.
The type can be any operation you want to perform like ADD_USER
, DELETE_RECORD
, GET_USERS
and so on.
If you've multiple words you can separate them with underscores like this { type: 'INCREMENT_NUMBER' }
.
Now, open the index.js
file and replace its contents with the following code:
import { createStore } from 'redux';
const reducer = (state = 0, action) => {
if (action.type === 'INCREMENT') {
return state + 1;
} else if (action.type === 'DECREMENT') {
return state - 1;
}
return state;
};
const store = createStore(reducer);
store.subscribe(() => {
console.log('current state', store.getState());
});
store.dispatch({
type: 'INCREMENT'
});
store.dispatch({
type: 'INCREMENT'
});
store.dispatch({
type: 'DECREMENT'
});
Now, If you run the application by executing the npm start
command from the terminal, you will see the following logs printed in the console.
As you can see for every action dispatched to the store, the store gets changed and so we're able to see the different values of the state in the console.
In the above code, our reducer function looks like this:
const reducer = (state = 0, action) => {
if (action.type === 'INCREMENT') {
return state + 1;
} else if (action.type === 'DECREMENT') {
return state - 1;
}
return state;
};
Whenever we call the store.dispatch
function, the reducer function will be called and whatever returned from the reducer will become the new value of the store.
So when the first time we dispatch an action to the store like this:
store.dispatch({
type: 'INCREMENT'
});
the first If condition inside the reducer function will be executed that will increment the state
value to 1
which was initially initialized to 0
using ES6 default parameter syntax and it will be returned from the reducer function.
Note that we're using the value of the state
to calculate the new value and we're not modifying the original state
value like this:
if (action.type === 'INCREMENT') {
state = state + 1;
return state;
}
So the above code is wrong because in reducer we should not modify the original state. Doing so will create issues in your application and is not recommended.
You will learn why we don't directly change the state and various ways of creating copies of array and object in detail in the course.
And because we've added store.subscribe
function in the index.js
file, we're getting notified about the changing store as we can see the logs in the console.
So when we again call the dispatch with type INCREMENT
, the first If condition will be executed again so 1 will be added to the previous state value which was 1 and the final state value will become 2.
Then we're dispatching the DECREMENT
action to the store like this:
store.dispatch({
type: 'DECREMENT'
});
which will execute the else condition inside the reducer and it will decrement the state value by 1 so 2 - 1 will become 1.
Note that, inside the reducer, we're also returning state at the end so If none of the condition matches, the default previous state will be returned from the function.
It's a common practice to use a switch statement inside the reducer instead of the if-else condition like this:
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
};
In addition to the type, we can also pass extra information as a part of the action.
Replace the contents of the index.js
file with the following code:
import { createStore } from 'redux';
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + action.payload;
case 'DECREMENT':
return state - action.payload;
default:
return state;
}
};
const store = createStore(reducer);
store.subscribe(() => {
console.log('current state', store.getState());
});
store.dispatch({
type: 'INCREMENT',
payload: 1
});
store.dispatch({
type: 'INCREMENT',
payload: 5
});
store.dispatch({
type: 'DECREMENT',
payload: 2
});
Now, If you run the application by executing the npm start
command from the terminal, you will see the following logs printed in the console.
Here, while dispatching an action to the store, we're passing a payload
with some value which we're using inside the reducer to increment or decrement the store value.
Here, we've used payload
as a property name but you can name it whatever you want.
Our reducer function looks like this now:
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + action.payload;
case 'DECREMENT':
return state - action.payload;
default:
return state;
}
};
So when we dispatch actions with type INCREMENT
like this:
store.dispatch({
type: 'INCREMENT',
payload: 1
});
store.dispatch({
type: 'INCREMENT',
payload: 5
});
the following code from the reducer will be executed
return state + action.payload;
which will first add 1 and then 5 to the previous value of the state so we go from 1 to 6 and because of the DECREMENT
action type
store.dispatch({
type: 'DECREMENT',
payload: 2
});
we go from 6 to 4 so the final value of the store will become 4.
Here's a Code Sandbox Demo.
Thanks for reading!
This was a quick introduction to Redux from my Mastering Redux course.
In the Mastering Redux course, you will learn:
- Basic and advanced Redux
- How to manage the complex state of array and objects
- How to use multiple reducers to manage complex redux state
- How to debug Redux application
- How to use Redux in React using react-redux library to make your app reactive.
- How to use redux-thunk library to handle async API calls and much more
and then finally we'll build a complete food ordering app from scratch with stripe integration for accepting payments and deploy it to the production.
Today is the last day to get the Mastering Redux course at discounted price.
So click the below image to get the course at just $12 instead of the original price of $19.
You will also get a free copy of my popular Mastering Modern JavaScript book If you purchase the course till 19th May 2021.
Want to stay up to date with regular content regarding JavaScript, React, Node.js? Follow me on LinkedIn.