Redux is one of confusing concepts that can be very challenging for a beginner getting started using it. From its fancy terminology like reducers,
dispatch
, payload,
to its additional packages like Redux-saga
, Redux-thunk
, Redux-promise
... one may struggle even finding where to start and easily get lost with these Redux Mumbo jumbo. In this guide, we will explain the basics of Redux from a beginner's perspective using simple plain human language. By the end of the article you will understand the basics of Redux and we will use our Redux knowledge to build a simple banking app.
Note: This article will use the traditional way of using Redux (without using redux-toolkit
). I intentionally made it this way to focus on explaining redux concepts with less confusing code. But In the following articles in this series, we will integrate redux-toolkit in our code and see precisely problems that the redux-toolkit
was created to solve.
Prerequisites
- We will be building everything from scratch; you only need VS code and node installed in your machine
Let's start by explaining some of the Redux terminologies
1. What exactly is Redux
When you visit the official Redux Website, you see this simple definition ==> Redux:
A Predictable State Container for JS Apps
But what does this even mean? How did they manage to make 7 words so difficult to understand?
First, what is the application state?
Generally, the state of your application is the situation or environment that your app is running in, and this state usually changes. For example, suppose you have a website like Facebook, when you just land on the website, we can say that the application is in the state where no user is logged in, but as soon as you log in, the state changes and now the app is the state where someone is logged in.
Let's take another example where someone visit a website and decide to use the dark mode by clicking it's button as soon as they switch mode everything will change on that page and we can say that the app was in state of light mode and is now in the state of dark mode.
In applications those changes are our state thing like is the user logged in?, is the page loading?, letters you are writing determines the state of our app and We need to track these changes if this state changes, right? For example, Facebook needs to know that someone is logged in so that they can be presented with their messages, and that's where Redux
comes in as 'a container of application state.
Basically the state of our application is a long object containing all these information that changes it can look like
let state = {
userLoggedIn: true,
mode: "dark",
}
Like now we can see that the user is logged in and chose to use the dark mode. This object will be managed by Redux and track all changes of our app state we can access the state from Redux whenever we want.
so the definition that Redux is "A Predictable State Container for JS Apps" means that Redux is so good at managing your application state that whenever the state in your JS application expect that it is recorded by Redux
2. Redux actions
In Redux actions are much similar real life actions, they describe how to do something. like the way you can have an action of Reading a book that's the same with Redux except that in Redux we are dealing with application state. As we saw that we constantly need to change our application state we need a way to tell Redux how to change the state, and that's where we use actions
In Redux, simply actions
are JavaScript objects that explains the action to do on our state. for example an action will look like this
const action1 = {
type: "DO_SOMETHING"
}
- Redux actions will always have a field of
type
which describe what to do. and this field is compulsory. note that by convention actions type is written in capital letter separated with underscores - Redux action can also have a field of
payload
which can be anything that give more detail about how to do the action but this field is optional.
Let's use an example. Let's say in real life you want to go to Simba supermarket to buy 10 red apples we can describe this as a Redux action like the Following
const action = {
type: 'BUY_APPLES',
payload: {
shop: 'Simba supermarket',
type: 'red',
number: 10,
}
};
In this example our action is just an object containing the type describing that we want to buy apples and in our case the payload is another object containing additional information of where to buy apples which type and how much to buy
for a more practical example let's in a TODO app you want to create an action that will add a new TODO to the state of TODOS list in our application the action could be like
const action = {
type: "ADD_TODO",
payload: "My first ToDo"
}
- Note how the payload can also be just a string as I don't have additional information to explain this action
Redux action creators
In Redux as the name says action creators
are functions that create actions but you may say that we already created action in the previous why would we need to use action creators? let's take the apples example mentioned above what if we wanted to buy 7 green apples from a different shop. the action is still the same we don't need create a new one we can just use a function that take input and return appropriate action
we can use something like
const createAction = (shopName, appleType, appleNumber) => {
return {
type: 'BUY_APPLES',
payload: {
shop: shopName,
type: appleType,
number: appleNumber,
},
};
};
action creators are function that just return the action whenever we want this action we can call that function customize those parameters and the action will be created for us
3. Redux Reducers
So we have a state and we have an action we want to do to the state how exactly do we tell redux to do the action and change the state. that's where "reducers" come in. Redux use fancy names so what in the world is a reducer? By definition
A Reducer is a pure function that takes the state of an application and action as arguments and returns a new state
Pure functions? Pure functions are functions that will always return the same output whenever they are given the same arguments. but isn't that what all function do? returning same results? well, not really
consider these functions
const functionA = (number)=>{
const sum = number + 2;
return sum;
};
let x = 2
const functionB = (number)=>{
const sum = number + x;
return sum;
}
these two functions may look like they are doing the same but functionA
is a pure function while functionB
is an impure function. This is because functionA
will always return the sum when a same number is passed but functionB
is depending on variable x
and if this value is changed functionB
will return a different sum.
There is more to pure functions, I recommend that you read these to articles to understand article1 article2
Back to the definition of a Reducer
A Reducer is a pure function that takes the state of an application and action as arguments and returns a new state
A reducer is just a function that will take the initial state and the action we want to do and return a new changed state. so a typical reducer will look something like
const reducer = (state, action)=>{
// do action ...
return newState
}
let use an example of counter where can start from zero and increment or decrement the number by one
we can have our initial state of 0 as value
const state = {
value: 0
}
we can have our actions like
const incrementAction = {
type: 'INCREMENT'
}
const decrementAction = {
type: 'INCREMENT'
}
Let's now create a reducer that will take a state and an action to return a new state. when we pass the increment action we will increase the current current state by 1 and when we pass decrement action we decrement it by 1
we will use switch statements to check which action we have
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT': {
const newState = { ...state };
newState.value = state.value + 1;
return newState;
}
case 'DECREMENT': {
const newState = { ...state };
newState.value = state.value - 1;
return newState;
}
}
};
Let's break this line by line
-
const reducer = (state, action)
: We are creating a reducer function that take initial state and action object as the definition of a reducer says -
switch (action.type)
As we have two actions we are using the switch statement to check the type of the action. You can also use if else statements if you want to -
const newState = { ...state }
: this is the most important part a reducer- is a pure function and will NEVER mutate the state passed to it as argument instead we create a new object and copy the previous state using spread operator. we are just creating a new object and copy everything from the state this means thatnewState
andstate
are different objects. -
newState.value = state.value + 1
: We are changing thevalue
field of newState to be the previous state value incremented or decremented by one according to the action type -
return newState
: we are returning new state as a reducer should return new state
the above code can be simplified to be
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, value: state.value + 1 };
case 'DECREMENT':
return { ...state, value: state.value - 1 };
}
};
4. Redux store
Now we have a state, we have actions that describes what to do with the state, we have a reducer function that implement our action and return the new state. It looks like we have everything we need we just need a better management of all of these code.
Basically we want that when we call the reducer function and return the new state, this new state should replace the old state and be our current state so the next time we do something we have a track of how the state changed in our app. To achieve this we need to have everything in the same place which is where the Redux store
comes in.
The Redux store
is like a real life store that holds records of how the state had changed in your application. for example when a user login the state changes, when they log out the state will change again Redux store will keep a track of these changes so if anything go wrong we can see exactly what happened and where it happened.
in Redux to access the store we need to create it first, the store is created using the function createStore()
and this function provide us functions that we will use to access and manipulate the state
In this guide we will be focusing on it's two functiongetState()
and dispatch()
-
getState()
: when you run this function the store will return the current state. -
dispatch()
: In Redux you don't really call the reducer and pass the action directly as we did before, instead you pass the action to this dispatch function of the store, and the store will will have your reducer and the sate and do everything for you.
this means that you don't need to worry about what's in the state you just dispatch (send) an action to the store, the store will call the reducer and pass the state and the action you dispatched. the reducer will do it's work as we saw earlier and when it returns the new state the store will automatically update the state to this new state.
It's like the way you go to the bank and you have an action of depositing money to your account the cashier will take your money do her work and add new amount to your account. With no effort done on your end
Don't worry if you don't understand all we said about Redux store let's see all in action as we build our simple banking app
PUTTING EVERYTHING TOGETHER: SIMPLE BANKING APP
Let's use what we learned to build a simple banking app where someone create accounts, view their balance, deposit or withdraw money from their account
follow these steps
1. Create a project
create a folder and open it in VS Code initialize a node project by running
npm init -y
- in the
package.json
add atype
field and set it value to"module"
as we will be using imports and export later
2. Install Redux
- install Redux by running the following command
npm install redux
// using yarn
yarn add redux
3. Create a redux store
- create a folder called
redux
and that's where our redux code will be - in the redux folder create a file and name it
store.js
this where we will configure our Redux store - in the 'store.js' file add the following code
import { legacy_createStore as createStore } from 'redux';
const store = createStore();
we are importing createStore from redux and we are creating a new store by invoking that function the createStore
function
4. Create a initial state
Let's have the initial state of our application let's say someone just created a new bank account and their basic information will be our object
- in the
store.js
right before we create thestore
variable we will add a variable of the initial state and will pass our initial state into the store so that it will store it for us the store.js should look like
import { legacy_createStore as createStore } from 'redux';
const initialState = {
accountOwner: 'John Doe',
address: 'Miami',
balance: 0,
};
const store = createStore(initialState);
export default store;
- we are creating an initial state that includes basic information of the owner and their balance is 0$ as they just created a new account and they don't have money yet.
5. Create action using action creator
Remember the actions and action creators we talked about earlier right? actions are object and action creators are function that return these objects
- in the redux folder create file called
actions.js
and we will add our action creators - let's create an action for Depositing money , withdrawing money, and changing address
in your actions.js add the following code
export const depositAction = (amount) => {
return {
type: 'DEPOSIT',
payload: amount,
};
};
export const withdrawAction = (amount) => {
return {
type: 'DEPOSIT',
payload: amount,
};
};
export const changeAdressAction = (newAdress) => {
return {
type: 'CHANGE_ADRESS',
payload: newAdress,
};
};
we are creating action creator functions that just return action with type and the payload the value we passed in
for instance the depositAction
will return an action with type DEPOSIT
and a payload of the amount you passed in.
6. Create a reducer
in the redux folder create a reducer.js
file which will contain our reducer
- in the
reducer.js
add the following code
const reducer = (state, action) => {
switch (action.type) {
case 'DEPOSIT':
return { ...state, balance: state.balance + action.payload };
case 'WITHDRAW':
return { ...state, balance: state.balance - action.payload };
case 'CHANGE_ADRESS':
return { ...state, address: action.payload };
default:
return state;
}
};
export default reducer;
- As always it's important that the reducer doesn't mutate the state passed. We create a new object and copy everything in the previous state and change the field we want to change
- in this case when the action is
DEPOSIT
we will change the balance to add amount in the payload to previous balance. the same thing withWITHDRAW
instead we subtract amount in the payload from previous balance - when the action is
CHANGE_ADRESS
we will only change the address field to the new address from the payload - If the action is not known by default we will do nothing we just return previous state unchanged
7. Pass the reducer to the store
Remember that we don't have to do anything ourselves the redux store will do everything for us therefore we need to provide the reducer to the store.
- back to the
store.js
import the reducer function and pass it to thecreateStore
function.
import { legacy_createStore as createStore } from 'redux';
import reducer from './reducer.js';
const initialState = {
accountOwner: 'John Doe',
address: 'Miami',
balance: 0,
};
const store = createStore(reducer, initialState);
export default store;
- we are importing reducer function from
reducer.js
and pass it to thecreateStore
function along with the initial state we had before Note that the reducer should be passed first ascreateStore
function expects the reducer to be the first argument
That's all configurations we need now lets test how everything works
8. Testing
in the root folder create an index.js
file and import the store and actions from redux folder.
- in the
index.js
add the following code
import {
changeAdressAction,
depositAction,
withdrawAction,
} from './redux/actions.js';
import store from './redux/store.js';
console.log('initialState:');
console.log(store.getState());
//
store.dispatch(depositAction(500));
console.log('New state after deposit:');
console.log(store.getState());
//
store.dispatch(changeAdressAction('Paris'));
console.log('New state after change address');
console.log(store.getState());
//
store.dispatch(withdrawAction(300));
console.log('New state after withdraw');
console.log(store.getState());
- to test everything we are just consoling the state by using
store.getState()
remember thatgetState
returns our current state - we are dispatching actions by using
store.dispatch()
and we pass in the function we want to dispatch after dispatching an action we are consoling the state again to see changes
Run
node index.js
in the terminal and you should see the following output
- you can see that after dispatching an action redux did updated our state
There you have it! you now understands the basics of Redux In the following article in this series we will look into how to use Redux-toolkit
to write cleaner code and integrate Redux in a real redux app that is more interactive.
For reference you can find code mentioned in this article on this github repo
Discussion (0)