DEV Community

Cover image for Redux explained from a beginner's perspective (simplified)
Ivad Yves HABIMANA
Ivad Yves HABIMANA

Posted on • Updated on

Redux explained from a beginner's perspective (simplified)

Redux is one of the tools that can be challenging for a beginner to get started using it. from its fancy terms like reducers, dispatch, payload, to a lot of packages built on top of it like Redux-saga, Redux-thunk, Redux-promise... one may struggle even to find where to start and easily get lost into these Redux Mumbo jumbos.
In this guide, we will go through the basics of Redux from a beginner's perspective using the simple plain English language. By the end of the article, you will have a strong understanding of Redux basics, and we will use our new Redux knowledge to build a simple banking application.

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.
I plan to make this a series and in the following articles, we will integrate redux-toolkit in our code and see precisely the problems that the redux-toolkit was created to solve.
Don't worry if you don't even know what redux or redux-toolkit we will cover everything from scratch

Prerequisites

  • We will be building everything from scratch; you only need VS code and node installed on 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 in which your app is running, and this state usually changes. For example, suppose you have a website like Facebook where users can log in and log out from their accounts,
when someone just lands on the website, we can say that the application is in a state where no user is logged in, but as soon as they enter their credentials and click the login button, the state changes, and now the app is the state where someone is logged in.

Not clear yet? Let's take another example, let's say on a website users can visit and decide to use the dark or light mode by toggling the theme slider. As soon as they switch modes everything will change in the app and the selected mode will be applied.
We can say that the app was in the state of light mode and is now in the state of dark mode.

As explained in the above examples our application behaves differently according to the current state and we need to track the state of the application to build modern information like is the user logged in?, is the page loading?, the key that the user pressed...
That's where Redux comes into the picture as a container of application state.

Basically in Redux, the state of our application is just a long JavaScript object containing all the information describing every change we need to keep track of.

For example, It can look like the following (note that the examples are simplified for the sake of clarity

let state = {
  userLoggedIn: true,
  mode: "dark",
}
Enter fullscreen mode Exit fullscreen mode

We can describe the above state by saying that the user is logged in and chose to use dark mode. When using Redux this object will be managed by Redux and it will track all changes we can access the updates from Redux whenever we want.

Back to the Redux definition

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 expects that it is recorded by Redux

Now that we have covered the Redux definition let's take a look at some of its concepts

2. Redux actions
Redux's actions are much similar to 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 the application state.

As we saw that we constantly need to change our application state according to different conditions, we need a way to tell Redux how to change the state, and that's where we use actions.

In Redux, an action is a simple JavaScript object that explains the action to perform on our state in order to manipulate it.

for example, an action can look like this

const action1 = {
type: "DO_SOMETHING"
}
Enter fullscreen mode Exit fullscreen mode
  • Redux actions will always have a property of type which describes what to do. and this property is compulsory. Note that by convention actions type is written in capital letters separated by underscores (also known as capital snake casing)
  • Redux action can optionally have a property called payload which can be anything that gives more detail about how to do the action but this field is optional.

Let's explain Redux actions more using 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,
  }
};

Enter fullscreen mode Exit fullscreen mode

In this example, our action is just an object containing the type describing what we want (to buy apples) and in our case, the payload is another object containing additional information about where to buy apples which type, and how much to buy.

For a more practical example let's say we have a ToDo app and in the app, we have a state of all TODOS, you would want to create an action that will add a new TODO to the state and the action could be like the following

const action = {
type: "ADD_TODO",
payload: "Walk the dog" // payload contains the actual task to do
}
Enter fullscreen mode Exit fullscreen mode
  • Note how the payload can also be just a string as in the current 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 we already created action in the previous why would we need to use action creators?

Generally, we use functions when we don't want to repeat the same code over again. In the above buying apples example what if we wanted to buy 7 green apples from a different shop? The action is still the same (buying apple) therefore we don't need to create brand new action we can just use a function that takes input and return appropriate action and can be reused whenever needed.

We can use something like

const createAction = (shopName, appleType, appleNumber) => {
  return {
    type: 'BUY_APPLES',
    payload: {
      shop: shopName,
      type: appleType,
      number: appleNumber,
    },
  };
};
Enter fullscreen mode Exit fullscreen mode

Action creators are functions that just return the action object and they can be given different arguments to provide new actions dynamically

3. Redux Reducers
So we now have the stat, and we have an action we want to do to change the state how exactly do we tell redux to do the action and actually change the state? that's where "reducers" come in.

Redux uses fancy names so what in the world is a reducer? By definition from the documentation

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 input.
But isn't that what all functions do? returning the same results? well..., not really

consider these two functions

const functionA = (number)=>{
  const sum = number + 2;
  return sum;
};

let x = 2 // keep attention here
const functionB = (number)=>{
  const sum = number + x;
  return sum;
}
Enter fullscreen mode Exit fullscreen mode

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 the same number is passed
On the other hand but functionB is depending on variable x and if this value is changed functionB will return a different sum. Another important behaviour of a reducer is that it should not change the values that are passed to it as input

There is more to pure functions, I recommend that you read these to articles to understand further 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 and it will perform the action we want, and return an updated state.

A reducer need to be pure so that it will always walk in a consistent way.

A reducer look something like

const reducer = (state, action)=>{
  // do action ...
  return newState
}
Enter fullscreen mode Exit fullscreen mode

let's use an example of a counter app that stores the value of the counter and we can have an action to increment or decrement the number by one

We can have 0 as our initial value of the counter

const state = {
 count: 0
}
Enter fullscreen mode Exit fullscreen mode

we can have our actions like

const incrementAction = {
type: 'INCREMENT'
}

const decrementAction = {
type: 'INCREMENT'
}
Enter fullscreen mode Exit fullscreen mode

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 state by 1 and when we pass the decrement action we decrement it by 1.
We will use switch statements to check which action we have and return the new value accordingly

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;
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Let's break the code line by line

  • const reducer = (state, action): We are creating a reducer function that takes the initial state action object as we saw from the definition

  • 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 an 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 that newState and state are different objects but they have the same values.

  • newState.value = state.value + 1: We are changing the value field of newState to be the previous state value incremented or decremented by one according to the action type

  • return newState: we are returning the new state as a reducer should return the 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 };
  }
};
Enter fullscreen mode Exit fullscreen mode

4. Redux store
Now we have a state, we have actions that describe what to do with the state, and we have a reducer function that implements our action and returns the new state. It looks like we have everything we need we just need better management of all of this code.

Basically, we want that whenever we call the reducer function and update the state, the new state should replace the old state and be our current state so the next time we perform another change 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, and when they log out the state will change again, Redux store will keep a track of these changes so that if anything goes 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 provides us with functions that we will use to access and manipulate the state

In this guide, we will be focusing on its two functions getState() and dispatch()

  • getState(): when you run this function the store will return the current value of the state and this value will always be the updated state.

  • dispatch(): In Redux you don't really call the reducer and pass the action directly as we have been doing before, instead you pass the action to the dispatch function, and the store will have access to the reducer and the state itself and it will 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 its work as we saw earlier and when it returns to 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 the new amount to your account with no effort done on your end

Don't worry if this you are confused by all of this, 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 creates 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
Enter fullscreen mode Exit fullscreen mode
  • in the package.json add a type 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
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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 the store 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;
Enter fullscreen mode Exit fullscreen mode
  • 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,
  };
};

Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
  • 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 with WITHDRAW 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 the createStore 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;

Enter fullscreen mode Exit fullscreen mode
  • we are importing reducer function from reducer.js and pass it to the createStore function along with the initial state we had before Note that the reducer should be passed first as createStore 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());

Enter fullscreen mode Exit fullscreen mode
  • to test everything we are just consoling the state by using store.getState() remember that getState 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

terminal 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

Top comments (1)

Collapse
 
ibrahimbagalwa profile image
Ibrahim Bagalwa

nice one