Redux Toolkit is intended to be the standard way to write Redux logic.
Now, what is redux? Redux is mainly used for state management.
We already understand that we should know about the basic principles of Redux before jumping to the Redux Toolkit.
Let's start. We have to know about the redux workflow. Redux has three parts.
i) Actions: Actions is the javascript object that contains some information from the app to the redux store. It has a type
property that determined something that happened in the app.
ii) Reducers: Reducers determine how the state is changed according to the type
of the actions
.
iii) Store: Store saves the application's state. Actions
can update store
using dispatch
keyword.
If you get confused between actions
and reducers
, then I will try to explain this again.
Actions
basically describe what to do through type
property and Reducers
determines how the task is done according to the type
property. Single store
just save the updated state.
Now jump to the code. You will be more clear about these.
const redux = require('redux');
//actions
const TheoryClass = "TheoryClass";
function theoryClass() {
return {
type: TheoryClass,
}
}
//reducers (state, action)
const initialState = {
countOfTheoryClass: 0,
}
const reducers = (state = initialState, action) => {
switch (action.type) {
case TheoryClass:
return {
...state,
countOfTheoryClass: state.countOfTheoryClass + 1,
}
default:
return state;
}
}
//store
//import createStore
const createStore = redux.createStore;
const store = createStore(reducers);
console.log('initial state', store.getState());
//subscribe- how the sore is updated
const unsubscribe = store.subscribe(() =>
console.log('Present State ', store.getState())
)
//access the store
store.dispatch(theoryClass());
store.dispatch(theoryClass());
//call unsubscribe to stop the store from updating
unsubscribe();
I created a node project, install the redux and initialize the redux.
Now, Most of you have two questions in your mind. How can I use redux without React? Why am I not using redux with react.js in this blog?
Redux is not only used with React.js. We can use this with any other programming languages and frameworks.
Now, we try to understand the redux core concept. This is the reason, I am not using redux with react.js in the above code. But I will describe later how to use Redux Toolkit with React.js and how Redux logic is working under the hood. So, nothing to bother about the syntax of the above code. Focus on understanding the core concept for now.
Let's explain the above code.
Actions
is returning a type TheoryClass
. Reducers
determine how the initial state
is manipulated for the action
's type
.
We declare an initial state
. There is a property countOfTheoryClass
that value is 0. Reducers
always received two parameters. Those are state
and action
.
We can access the action's type using action.type
and write the logic for any type of action in the switch case
. In TheoryClass
case, copy the current state and update the state of countOfTheoryClass
. First copy the state using ...state
, because we shouldn't manipulate all properties of the initial state.
For example, if the initial state is like the bellow
const initialState = {
countOfTheoryClass: 0,
countOfBreak:0
}
Now, we don't want to change the countOfBreak
, so the right approach is to copy the state and update only the property that you need.
Using this line const store = createStore(reducers);
, reducers
connceted to the store.
We can access the state using store.getState()
.
store.dispatch(theoryClass());
revokes the actions
.
If we run the above code, the output will be like this.
More Reducers: How to handle if you need more reducers? Let's think, you need how many practical classes happened and how many were canceled.
const redux = require('redux');
const combineReducers = redux.combineReducers;
//actions
const TheoryClass = "TheoryClass";
const TheoryClassCancel = "TheoryClassCancel";
function theoryClass() {
return {
type: TheoryClass,
}
}
function theoryClassCancel() {
return {
type: TheoryClassCancel,
}
}
const PracticalClass = "PracticalClass";
const PracticalClassCancel = "PracticalClassCancel";
function practicalClass() {
return {
type: PracticalClass,
}
}
function practicalClassCancel() {
return {
type: PracticalClassCancel,
}
}
//reducers (state, action)
const initialState = {
countOfTheoryClass: 0,
}
const initialPracticalState = {
countOfPracticalClass: 0,
}
const reducers = (state = initialState, action) => {
switch (action.type) {
case TheoryClass:
return {
...state,
countOfTheoryClass: state.countOfTheoryClass + 1,
}
case TheoryClassCancel:
return {
...state,
countOfTheoryClass: state.countOfTheoryClass - 1,
}
default:
return state;
}
}
const reducersPractical = (state = initialPracticalState, action) => {
switch (action.type) {
case PracticalClass:
return {
...state,
countOfPracticalClass: state.countOfPracticalClass + 1,
}
case PracticalClassCancel:
return {
...state,
countOfPracticalClass: state.countOfPracticalClass - 1,
}
default:
return state;
}
}
//store
const createStore = redux.createStore;
const rootReducer = combineReducers({
theoryClassReducer: reducers,
practicalClassReducer: reducersPractical,
})
const store = createStore(rootReducer);
console.log('initial state', store.getState());
//subscribe- how the sore is updated
const unsubscribe = store.subscribe(() =>
console.log('Present State ', store.getState())
)
//access the store
// store.dispatch(theoryClass());
// store.dispatch(theoryClass());
// store.dispatch(theoryClassCancel());
//bindActionCreators (actionCreator, dispatch)
const bindActionCreators = redux.bindActionCreators;
const action = bindActionCreators({ theoryClass, theoryClassCancel }, store.dispatch);
const practicalAction = bindActionCreators({ practicalClass, practicalClassCancel }, store.dispatch);
action.theoryClass();
action.theoryClass();
action.theoryClassCancel();
practicalAction.practicalClass();
practicalAction.practicalClass();
practicalAction.practicalClassCancel();
//call unsubscribe to stop the store from updating
unsubscribe();
you see two new keywords, combineReducers
and bindActionCreators
. bindActionCreators
helps to manage and dispatch all actions
together.
For example: If we want to dispatch store
with actions
, we write this,
store.dispatch(theoryClass());
store.dispatch(theoryClass());
store.dispatch(theoryClassCancel());
using bindActionCreators
we write the same thing like this
const action = bindActionCreators({ theoryClass, theoryClassCancel }, store.dispatch);
action.theoryClass();
action.theoryClass();
action.theoryClassCancel();
Let's Jump to the Redux Tookit
We will convert the above code with redux tookit and react.js.
There are some package buildin installed in the Redux Toolkit aka RTK.
i) Redux Thunk ii) immer and iii) redux devtools
We have introduced usedispatch
, useSelector
and slice
.
usedispatch: usedispatch
is a hook that invoked actions
. It works like dispatch
in the redux.
Now, create a react app and install redux toolkit
, react-redux
and redux
.
Create a folder named features
in src
folder. Create a folder theory
in the feature
. You can create your file structure like this.
theroySlice.js
import { createSlice } from '@reduxjs/toolkit'
const initialState = {
countOfTheoryClass: 0,
}
const theorySlice = createSlice({
name: "theory",
initialState,
reducers: {
theoryClass: (state, actions) => {
state.countOfTheoryClass += actions.payload;
},
theoryClassCancel: (state, actions) => {
state.countOfTheoryClass -= actions.payload;
}
}
});
export const { theoryClass, theoryClassCancel } = theorySlice.actions
export default theorySlice.reducer
Here we declare an initial state where countOfTheoryClass
value is 0.
const initialState = {
countOfTheoryClass: 0,
}
Now, give a name to the slice, and declare the initial state and reducers.
Then, go to the app
folder and create store
file in the folder.
store.js
import { configureStore } from "@reduxjs/toolkit";
import theorySlice from "../features/theory/theorySlice";
import practicalSlice from "../features/practical/practicalSlice";
const store = configureStore({
reducer: {
theory: theorySlice,
}
});
export default store;
Here, just call the call reducers and export the store
.
Don't forget to add provider
and store
to the index.js
file.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './app/store'
import { Provider } from 'react-redux'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
Rapped the app
file with Provider
.
Now, time to call the data from the state and show it on the client-side.
theory.js
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { theoryClass, theoryClassCancel } from './theorySlice';
const Theory = () => {
const theory = useSelector(state => state.theory.countOfTheoryClass);
const dispatch = useDispatch();
return (
<div>
<p>Total Theory class: {theory}</p>
<button onClick={() => dispatch(theoryClass(1))}>+</button>
<button onClick={() => dispatch(theoryClassCancel(1))}>-</button>
</div>
);
};
export default Theory;
useSelector
call the present state and useDispatch
call the actions
. In this case, actions
are in the theorySlice.js
file.
The same steps are followed for practicalSlice.js
.
import { createSlice } from '@reduxjs/toolkit'
const initialState = {
countOfPracticalClass: 0,
}
const practicalSlice = createSlice({
name: "practical",
initialState,
reducers: {
practicalClass: (state, actions) => {
state.countOfPracticalClass += actions.payload;
},
practicalClassCancel: (state, actions) => {
state.countOfPracticalClass -= actions.payload;
}
}
});
export const { practicalClass, practicalClassCancel } = practicalSlice.actions
export default practicalSlice.reducer
parctical.js
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { practicalClass, practicalClassCancel } from './practicalSlice';
export const Practical = () => {
const dispatch = useDispatch();
const practical = useSelector(state => state.practical.countOfPracticalClass);
return (
<div>
<p>Total Practical Class:{practical}</p>
<button onClick={() => dispatch(practicalClass(1))}>+</button>
<button onClick={() => dispatch(practicalClassCancel(1))}>-</button>
</div>
);
};
Install the redux dev tools
extensions and explore the change of actions and state.
Now, start to think about complex cases. One action
is dependent on another action
. In our example, If the same teacher takes both the theory and practical classes. When he is absent then both classes will be canceled. In this case, if the theory is canceled then the practical class will be canceled.
To solve this complex case, we will use extraReducers
.
pactricalSlice.js
import { createSlice } from '@reduxjs/toolkit'
import { theoryClassCancel } from '../theory/theorySlice';
const initialState = {
countOfPracticalClass: 10,
}
const practicalSlice = createSlice({
name: "practical",
initialState,
reducers: {
practicalClass: (state, actions) => {
state.countOfPracticalClass += actions.payload;
},
practicalClassCancel: (state, actions) => {
state.countOfPracticalClass -= actions.payload;
}
},
extraReducers: builder => {
builder.addCase(theoryClassCancel, (state) => {
state.countOfPracticalClass -= 1;
}
);
}
});
export const { practicalClass, practicalClassCancel } = practicalSlice.actions
export default practicalSlice.reducer
Now, Check the actions
and state
value using redux dev tools
and explore the changes in value.
How to call APIs using redux toolkit. Let's explore.
Let's assume this is the API of students. https://jsonplaceholder.typicode.com/users
userSlice.js
import axios from 'axios';
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
const initialState = {
users: [],
loading: false,
error: null,
}
export const fetchUser = createAsyncThunk('user/fetchUser', () => {
return axios
.get('https://jsonplaceholder.typicode.com/users')
.then(response => response.data)
})
const userSlice = createSlice({
name: 'user',
initialState,
extraReducers: builder => {
builder.addCase(fetchUser.pending, state => {
state.loading = true
}
)
builder.addCase(fetchUser.fulfilled, (state, action) => {
state.users = action.payload
state.loading = false
state.error = null
}
)
builder.addCase(fetchUser.rejected, (state, action) => {
state.error = action.error.message
state.loading = false
state.users = []
}
)
}
});
export default userSlice.reducer
createAsyncThunk
is the build-in function of redux toolkit. It accepts two parameters, actions, and a call-back function. The rest of the terms I explained before.
user.js
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchUser } from './userSlice';
const User = () => {
const users = useSelector(state => state.users.users);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchUser());
})
return (
<div>
{
users.map((user, index) => {
return <p key={index}>{user.name}</p>
})
}
</div>
);
};
export default User;
Don't forget to add userSlice
in the store
.
import { configureStore } from "@reduxjs/toolkit";
import theorySlice from "../features/theory/theorySlice";
import practicalSlice from "../features/practical/practicalSlice";
import usersSlice from "../features/users/userSlice";
const store = configureStore({
reducer: {
theory: theorySlice,
practical: practicalSlice,
users: usersSlice,
}
});
export default store;
That's it for now. Hopefully, you get the full picture of the Redux toolkit from this blog.
Happy Coding ๐จโ๐ป