When I started learning Redux, there were very less resources on the web for beginners with less complication and more demonstration to show how Redux works and what is the easiest way to understand the flow of Redux. This article would help you understand both, the theoretical and the practical part of Redux.
We will be using the following packages to look at how Redux work with ReactJS, it is same on React Native as well.
- ReactJS app created with CRA
- React Redux
- React Router
Theoretical Part
There are three main things that you need to understand about redux
- Store
Consider store as a local storage but fast. The data fetching and storing is so fast and it's not asynchronous that's why redux is so fast and responsive.
- Actions
Actions are just like methods. Major portion of logic building is done in action and you can also use different middle-wares for async requests etc. After that, the action tells the reducer to do something with the data.
- Reducer
Reducer is a function that can be called as a decision maker. Action tells the reducer what to do, after the decision reducer changed the state of the store and returns the new one.
Looking at the image above, you can somehow get the idea of how the data is being passed on to the component.
We can start with the View which is the component you want the data in. You are going to call an action which will perform all the instruction you wrote in it.
Later on, it will dispatch the action and the reducer inside the store will decide what to do with the action that was dispatched. Now the reducer executes the condition that satisfies the type of action that was dispatched before, and the reducer will then change the old state of the store and return the new one to the component via props.
We will discuss how the things are working using the props in our practical portion using code snippets, so it becomes more clear!
Practical Part
Practical Part
For the practical portion we will be creating a very simple todo application like this Link that will perform a CRUD operation on redux store. To start we will initialise the store first in our application which is created using Create React Application.
You would have to install react-redux package in your project using npm or yarn.
For yarn you can use
yarn add react-redux
For npm you can use
npm install react-redux
We will also be using a middleware to persist the data in store which is optional. Upon refresh it will preserve the previous state of the redux store and your data will not go away!
To setup the store we will use the App.js and following code snippet which is self explanatory.
import React from "react";
import "antd/dist/antd.css";
import "./index.css";
// Redux Imports
import { Provider } from "react-redux";
import Reducer from "./Redux/Reducer";
import { createStore } from "redux";
// Components import
import ListTodos from "./Screen/ListTodos";
const store = createStore(Reducer);
function App() {
return (
<Provider store={store}>
<ListTodos />
</Provider>
);
}
export default App;
In the above snippet you can see that we are using a createStore()
method from redux and passed on to the Provider component. Provider components makes the Redux store available to all the nested components inside the application.
Inside the Provider component we can write the rest of the code for the application such as routing etc.
Now we have 2 steps to complete the setup of redux
- Reducers
- Actions
Reducer is where the structure of our entities will be defined. Following snippet shows how a reducer is defined:
import {
DELETE_A_TODO,
ADD_A_TODO,
MARK_TODO_STATUS,
} from "../../Constants/Todos";
const initialState = {
todosList: [],
};
function todosReducer(state = initialState, action) {
if (action.type === ADD_A_TODO) {
return {
...state,
todosList: [action.payload, ...state.todosList],
};
}
if (action.type === MARK_TODO_STATUS) {
let newObject = [...state.todosList];
newObject[action.payload.index].status = action.payload.status;
return {
...state,
todosList: newObject,
};
}
if (action.type === DELETE_A_TODO) {
let newObject = [...state.todosList];
let index = newObject.findIndex((item) => {
return item.key === action.payload;
});
newObject.splice(index, 1);
return {
...state,
todosList: newObject,
};
}
return state;
}
export default todosReducer;
As you can see that the reducer is just a function with conditions inside, that will conclude what type of action is to be performed.
But, if you look at the top. We have the initial value of the store which is just an array of todosList: []
where we will be storing our todos and performing CRUD operations on.
That is all you need to focus on right now. Once we call different actions. We will look at how the dispatched action is being processed inside the reducer.
Next up, we will be setting our actions. We will have only three actions in our small application.
1) Add a todo
2) Mark todo status (Done, Pending)
3) Delete a todo
import {
ADD_A_TODO,
MARK_TODO_STATUS,
DELETE_A_TODO,
} from "../Constants/Todos";
export const addATodo = (todo) => {
return {
type: ADD_A_TODO,
payload: todo,
};
};
export const deleteATodo = (key) => {
return {
type: DELETE_A_TODO,
payload: key,
};
};
export const markTodoStatus = (data) => {
return { type: MARK_TODO_STATUS, payload: data };
};
The actions above are methods that are returning plain objects. Once the action is dispatched by the component. It goes to the reducer with the type of reducer.
- What is type of the action?
I have declared constants of plain strings to keep the code clean. They are just unique strings so the reducer can identify what type of action is dispatched.
Then, there is a payload key using which you can send any kind of data to the reducer. You can also process the data before sending it to the reducer inside the action. And you can also do the minor customisation of the data inside the reducer. We will be going with the latter one and process the data inside the reducer as they are just minor tasks that will be performed on the todoList inside the reducer state.
We will move on to the main portion, as the setup for the Redux flow is complete. All you need to do is to dispatch the action and redux will do the magic for you!
- Dispatching actions inside a view
Before moving towards the code side. We have to discuss three methods
connect()()
Connect method is provide by react-redux package which allows you to connect any component with the redux tree. So you can have access to the state and dispatch method. You have to pass 2 objects mapDispatchToProps, mapStateToProps which we will talk about later in the next point. And we have to pass the Component that we are working on.
mapDispatchToProps
mapDispatchToProps is a plain object in which you pass the actions that you created. And connect will attach the dispatch method with those actions so you can dispatch the actions. The actions will then be accessible via props of the component you passed inside the connect method.
mapStateToProps
mapStateToProps is a method that receives a callback param using which you can access the current state of the entire store. And you can access only the keys of the store that you need inside the function and return it. Once done, those keys will be accessible inside the component via props.
The snippet below shows how connect uses the component and uses mapDispatchToProps and mapStateToProps to map the state and actions with the component you are in.
const mapDispatchToProps = {
markTodoStatus,
addATodo,
deleteATodo,
};
const mapStateToProps = (state) => {
return {
todos: state.todos.todosList,
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ListTodos);
The mapDispatchToProps
is just accepting the actions that we created and sending inside the connect method which will be later accessible inside the component ListTodos
mapStateToProps is just accepting the current state of the store and only getting the todosList
from the reducer todos
and returning that inside an object. Which will also be later accessible inside the component.
Now, this is where the real magic happens. Wherever you are inside the hierarchy of your code. All you need to do is to connect any component with redux and you can utilise the data or change the data anywhere inside the application. That's how the state becomes so easy to manage inside a React app using Redux.
Last but not least, we need to discuss how we are managing the data inside the reducer that is passed when any action is dispatched. We will follow the whole hierarchy of how the method is dispatched inside the component.
After using dispatchToProps
parameter in connect()()
method. We will have access to any action that was passed inside dispatchToProps
object. Now you can access that particular action inside your component and call it using
props.addATodo({
key: props.todos.length,
task: "Do Something",
status: false,
priority: "Important",
};)
Once the action is called using the above code snippet it goes to the reducer and looks at what type of action is performed. If you look at the actions we defined we have addATodo
action inside our action.js
file.
export const addATodo = (todo) => {
return {
type: ADD_A_TODO,
payload: todo,
};
};
Once it's dispatched the whole flow is shifted towards the reducer. Reducer then looks at what type of action was dispatched and it changes the state of redux store accordingly.
if (action.type === ADD_A_TODO) {
return {
...state,
todosList: [action.payload, ...state.todosList],
};
}
As we have wrote the instructions to append the payload sent by the action inside the previous state of the store.
After that you will see that the data will be available in TodoList component. For testing you can place some console.log()
to see how the data is being passed and ends up inside the component. After that if you try to access that data in any component and use mapStateToProps
method. It will return you data even if that component is nested 10 times deep down the hierarchy!
By this you will get the idea of how the data flow of a very simple application is being controlled. You can have a look at the code the GitHub repository link is here Link. Fire up the repository code by running yarn install
or npm install
and see the application in action. You can use this concept to map it in your applications and make your state management easier.
Last we will be looking at how we can implement the redux persist middleware to make sure once the browser screen is refreshed. The data is not lost and it stays in your application.
Here is the link to How to add redux-persist in your react/react-native application
Top comments (1)
amazing brother