Table of contents
INTRODUCTION
Redux is a predictable state container for javascript applications. It manages and stores application states in a more structured and maintainable way, its predictability property comes with merits like keeping track of your states in the state container. For this to be possible a pattern is enforced to ensure all the state transitions are explicit based on the action dispatched and can be tracked.
In Redux, the state represents the entire state of your application. It is a single JavaScript object that holds the data your application needs. The state object is stored in the Redux store, which is responsible for managing the state and updating it when actions are dispatched. ln other words the state of an app is the state that is shared by all of the individual components of that app.
The state in Redux is immutable, meaning that it cannot be modified directly. Instead, when an action is dispatched, a new state object is created based on the previous state and the action. This new state object replaces the previous state in the store. This approach makes it easier to track changes in the state and enables features like time-travel debugging.
installation of redux to your project,run this command in your project directory
npm init
npm i redux
const redux = require('redux')
//is used in a Node.js environment to import the Redux library. This line uses the CommonJS module system to import the Redux library and assign it to a constant variable named redux. Once you have the Redux library imported, you can access its functions and methods to create and manage your application's state
ACTIONS
The only way your application can interact with the store in order to update the state of your application, you need to let redux know about that with an action, not allowed to directly update the state object. Generally, actions carry some information from your application to the store, these actions are plain javascript objects.
Each action has a type property, which is a string that describes the action's purpose. Optionally, actions can also have a payload property, which contains any additional data needed to update the state.
To create actions in Redux, follow these steps:
Define action types: Action types are constants that represent the different actions that can be dispatched to update the state. They are usually defined as string constants to avoid typos and make it easier to manage the actions.
const FETCH_USER_REQUESTED ='FETCH_USER_REQUESTED' const FETCH_USER_SUCCESS ='FETCH_USER_SUCCESS' const FETCH_USER_FAILED =' FETCH_USER_FAILED'
Create action creators: Action creators are functions that return action objects. These objects have a type property, which corresponds to an action type, and an optional payload property, which contains any additional data needed to update the state.
//fetchUserRequested: This action creator is called when the user fetching process starts. It returns an action with the FETCH_USER_REQUESTED type. const fetchUserRequested = ()=>{ return{ type:FETCH_USER_REQUESTED } } //fetchUserSuccess: This action creator is called when the user fetching process is successful. It takes an array of users as an argument and returns an action with the FETCH_USER_SUCCESS type and the users as the payload. const fetchUserSuccess = users =>{ return{ type:FETCH_USER_SUCCESS, payload:users } } //fetchUserFailed: This action creator is called when the user fetching process fails. It takes an error as an argument and returns an action with the FETCH_USER_FAILED type and the error as the payload. const fetchUserFailed = error =>{ return{ type:FETCH_USER_FAILED, payload: error } }
REDUCERS
To specify how the state tree is modified based on actions, you write pure reducers that specify how the application's state changes in response to the action that is sent to the store.
Reducers are pure functions that accept state and action as arguments in which the state is equal to the initial state of the store and returns the new state of the application.
To create a reducer in Redux, follow these steps:
- Define the initial state: The initial state is an object that represents the default state of your application.
const initialState ={
loading: false,
user:[],
error:''
}
- Define the reducer function: The reducer function takes the initial state and an action as arguments and returns a new state based on the action type and payload. It should always return a new object, not modify the existing state.
const reducer = (state = initialState,action ) =>{
switch(action.type){
case FETCH_USER_REQUESTED:
return{
...state,
loading:true
}
case FETCH_USER_SUCCESS:
return{
loading:false,
user: action.payload,
error:''
}
case FETCH_USER_FAILED:
return{
loading:false,
user:[],
error:action.payload
}
default:
return state
}
}
STORE
In redux the store holds your application's state, you access your state via getState(), and the state is updated by dispatch(action).
The subscribe method allows you to listen for changes in the store. It takes a callback function as an argument, which will be called whenever the state changes. The subscribe method returns an unsubscribe function that can be called to stop listening for updates.
Calling the unsubscribe function returned by the subscribe method will stop the callback function from being called when the state changes.
unsubscribe();
//Calling the unsubscribe function returned by the subscribe method will stop the callback function from being called when the state changes.
The fetchUsers function is a thunk action creator that returns a function that takes dispatch as an argument. Inside this function, you are dispatching the fetchUserRequested action, making an API call using Axios, and then dispatching either the fetchUserSuccess or fetchUserFailed actions based on the API call's result.
const fetchUsers = () =>{
return function( dispatch){
dispatch(fetchUserRequested ())
axios.get('https://jsonplaceholder.typicode.com/users').then(
(response) =>{
const users = response.data.map((user)=> user.id)
dispatch(fetchUserSuccess(users))
})
.catch((error)=>{
dispatch(fetchUserFailed(error.message))
})
}
}
- Create the Redux store with the Thunk middleware:
const redux = require('redux')
const thunkMiddleware = require('redux-thunk').default
const axios = require('axios')
const applyMiddleware = redux.applyMiddleware
//we are using Redux Thunk middleware to handle asynchronous actions in your Redux store.
const store = redux.createStore(reducer,applyMiddleware(thunkMiddleware))
store.subscribe(()=>{console.log(store.getState())})
store.dispatch(fetchUsers)
Here, you create the Redux store using the createStore function, passing in your reducer, and applying the thunkMiddleware using the applyMiddleware function. This code sets up a subscription to the store so that the current state is logged to the console whenever the state changes. Finally, you dispatch the fetchUsers thunk action, which will trigger the API call and update the store's state based on the API call's result.
Redux Toolkit(RTK)
Redux Toolkit is an opinionated, batteries-included toolset for efficient Redux development. It is recommended to be the standard way to write Redux logic, and we strongly recommend that you use it.
Redux toolkit is referred to as "opinionated" because it provides a set of recommended practices, convections, and utility functions that encourage a specific way of structuring and organizing redux code.
Redux toolkit is also considered "batteries-included" because it comes with built-in utilities. These built-in features help developers with reduced amount of boiler-plate code they need to write and this helps them to get started quickly.
installation of the redux toolkit to your project, run this command in your project directory
npm install @reduxjs/toolkit
CREATESLICE
Createslice is a function that generates a slice of the redux store, including the reducer, function action creators, and current state.
therefore it helps to reduce boilerplate code and makes it easier to define a redux store.
In redux, we write case reducer functions inside the reducer objects and give them readable names. However, with create slice in the toolkit will automatically generate action creators that correspond to each case reducer function we have and it automatically returns the existing state as the default state.
Createslice allows us to safely "mutate "our state ,it utilises a library called immer inside. Immer uses a special javascript tool called proxy to swap data you provide and lets you write code that mutate although Immer tracks all the changes you've tried to make and then uses that list of changes to return a safely immutably updated value.
import { createSlice } from "@reduxjs/toolkit";
import { SliderData }from '../../assests/data/chunkData'
export const sliderSlice = createSlice({
name:'slider',
initialState:{
value: 0,
length:SliderData.length,
},
reducers:{
leftSlide(state,action){
state.value = action.payload < state.length -1 ? 0 : action.payload
},
rightSlide(state,action){
state.value = action.payload > state.length -1 ? 0 : action.payload
},
dotSlide(state,action){
const slide = action.payload
state.value = slide
}
}
})
export const {leftSlide,rightSlide,dotSlide} =sliderSlice.actions
export default sliderSlice.reducer
Explanation:
The
createSlice
function is imported from the@reduxjs/toolkit
package, which simplifies the process of creating Redux slices.The
SliderData
is imported from the../../assets/data/chunkData
file. It's assumed that the file contains data related to the slider component.The
sliderSlice
is created usingcreateSlice
and provides a name for the slice and an initial state object.The initial state object includes two properties:
value
initialized as 0 andlength
initialized as the length of theSliderData
.The
reducers
object defines the actions that can be dispatched to modify the state.The
leftSlide
andrightSlide
reducers update thevalue
property based on theaction.payload
value. If the payload is less than the length minus 1, it setsvalue
to 0; otherwise, it sets it to the payload value.The
dotSlide
reducer sets thevalue
property to the value provided in theaction.payload
.The
export
statement exports the action creatorsleftSlide
,rightSlide
, anddotSlide
from the slice.The
default
export is the reducer function for the slider slice.
This code defines a Redux slice for managing the state of a slider component. It provides actions for moving the slider left, right, or directly to a specific dot, and the reducer updates the state accordingly.
CONFIGURE STORE
configureStore is a utility function provided by Redux Toolkit that simplifies the process of setting up a Redux store. It automatically applies a set of recommended middleware, configures the Redux DevTools extension, and combines the provided reducers.
That one call to configure the store does all the work for us like instead of combined when we have more than one reducer.
it created a redux store using that root reducer.A root reducer is a top-level reducer in a Redux application that combines all the individual reducers responsible for managing different parts of the application state. The root reducer is created using the combineReducers function provided by Redux or Redux Toolkit
it automatically added thunk middleware and so adds more middleware to check for common mistake like accidentally mutating the state .Middleware provides a third-party extension point between dispatching the action , and the moment it reaches the reducer in other words it is a suggested way to extend redux with custom functionality.
import { configureStore } from '@reduxjs/toolkit';
import slideReducer from '../features/slices/sliderSlice';
import itemReducer from '../features/slices/itemSlice'
import cartReducer from '../features/slices/cartSlice'
export const store = configureStore({
reducer: {
slider: slideReducer,
item: itemReducer,
cart:cartReducer
},
});
Explanation:
The
configureStore
function is imported from@reduxjs/toolkit
. It is used to create the Redux store.The
slideReducer
,itemReducer
, andcartReducer
are imported from different slice files. It is assumed that these files contain the reducer functions for the corresponding slices (slider
,item
,cart
).The
store
constant is created by callingconfigureStore
with a configuration object.The configuration object has a
reducer
property that takes an object with key-value pairs.Each key in the
reducer
object represents a slice name (slider
,item
,cart
), and the corresponding value is the reducer function for that slice.The
configureStore
function combines the reducers from different slices into a single store.The created store is assigned to the
store
constant, which can be used to interact with the Redux store in the application.
In summary, this code configures the Redux store by combining multiple reducers from different slices into a single store using Redux Toolkit's configureStore
function.
createAsyncThunk
createAsyncThunk is a utility function provided by Redux Toolkit that simplifies the process of handling asynchronous actions in Redux. It generates a set of action creators and action types for a given async operation, and automatically dispatches the appropriate actions based on the operation's state.
import { createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
export const fetchUsers = createAsyncThunk(
'users/fetch',
async (_, { dispatch, rejectWithValue }) => {
try {
dispatch(fetchUserRequested());
const response = await axios.get('https://jsonplaceholder.typicode.com/users');
const users = response.data.map((user) => user.id);
return users;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
Explanation:
createAsyncThunk
is imported from@reduxjs/toolkit
to create an async thunk action.The
fetchUsers
thunk action is created usingcreateAsyncThunk
. It takes two arguments: a string representing the action type, and an async function that performs the asynchronous logic.Inside the async function, the first argument
_
is used to indicate that it is not being used in this case. You can replace it with any necessary arguments for your specific use case.Within the async function, the
dispatch
andrejectWithValue
functions are provided as the second argument to enable dispatching actions and handling errors.The
fetchUserRequested
action is dispatched usingdispatch(fetchUserRequested())
to indicate that the user fetching process has started.The
axios.get
method is used to make an HTTP GET request to the specified URL.The response data is mapped to extract the user IDs.
The
fetchUserSuccess
action is dispatched usingdispatch(fetchUserSuccess(users))
to store the retrieved user IDs in the state.If an error occurs during the HTTP request or mapping process, the
fetchUserFailed
action is dispatched usingdispatch(fetchUserFailed(error.message))
to handle the failure.The
error.message
is passed torejectWithValue
to include the error message in the rejected action payload.
By regenerating the fetchUsers
function using createAsyncThunk
, you can handle the asynchronous fetching of users and handle success and failure scenarios in a more streamlined and standardized way using Redux Toolkit.