Have you ever wanted to provide state to your whole app or different parts of your app?
Have you ever used Context API and have suffered some disadvantages it comes with?
Such as have a deeply nested State providers in some parts of your app.
I know you can avoid deeply nested providers by creating one big context which stores all your app's state and creating its provider to wrap your root component, but this resolves in creating one big context which manages a lot of things and in turn becomes difficult to manage and maintain.
Redux is the state management tool that will come to our rescue.
Lets get started by understand how Redux actually works.
Redux works by creating one central store which contains all your states in your entire application.
It then provides the state to your app by wrapping your root component with the Provider that comes from react-redux.
Now, you would be thinking how will a component get access to the state and how they will re-build whenever some state changes in the store.
You have to understand that a component must not and should not change the state in the store directly. What happens is that, there is a reducer which is responsible for mutating the state in the store.
Here is a better picture
- A component triggers/dispatches some action to the reducer
- The reducer checks the type of action that was triggered.
- It(reducer) then changes/mutates the state
- All subscribed components listening to that state automatically update with the new state.
NOW LETS GET INTO CODING
Lets build a simple counter
First make sure you have your project setup already by running
npx create-react-app my-app
-
Now we have to install the react-redux package together with redux toolkit by running
npm install @reduxjs/toolkit react-redux
.- React toolkit does alot of things under hood and hides most of the complexities when using only redux
- React-redux also makes our life simpler by connecting components to store, subscribing to store, etc...
Now we'll create our store that will hold all our app wide state
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore();
export default store;
- Now we'll provide the states of the store to our app by wrapping it to our root component, in my case around the
<App />
in theindex.js
import React from 'react';
import { Provider } from 'react-redux';
import ReactDOM from 'react-dom/client';
import store from './store/store';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Provider store={store} ><App /></Provider>);
- Now lets create our counter reducer who's job is to mutate the state of the app.
import { createSlice } from "@reduxjs/toolkit";
const initialState = { counter: 0 };
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
increaseBy5(state, action) {
state.counter = state.counter + action.payload;
},
},
});
const counterActions = counterSlice.actions;
export default counterActions;
Lets try to understand the code above.
createSlice is a function from react toolkit that is used to group states that relate to each other states.
For example we can a slice for counter, another for authentication, themes, and many others.
In our case we only one one slice for the counter.We then provide our initial state that a component will initially use when rendered.
The createSlice function from the toolkit has there main properties that must be set.
name - This describes the name of the slice you're creating
initialState - The initial state the slice comes with
reducers - This is an object that contains the types of functions you want to perform and depending on the dispatch action from within our component.
It will return a new state with the updated changes. The action method receives two arguments, the state and action.
The state basically is the current state of your slice and the action has a payload argument that can be used in the action types functions.
As you can see we're receiving a payload on theincreaseBy5
method which will be passed on from the components when dispatching an action.counterActions - we will use them to dispatch the type of actions from our components
- Now we will add the counter slice to our store by targeting the reducer property hooked to it
import { counterSlice } from "../slices/counter-slice";
const store = configureStore({
reducer: counterSlice.reducer,
});
export default store;
- Finally we can access and dispatch actions from our components
import classes from './Counter.module.css';
import { useSelector, useDispatch } from 'react-redux';
import counterActions from '../slices/counter-slice';
const Counter = () => {
const Counter = useSelector(state => state.counter)
const dispatch = useDispatch();
function increase() {
dispatch(counterActions.increment());
}
function decrease() {
dispatch(counterActions.decrement());
}
function increaseBy5() {
dispatch(counterActions.increaseBy5(5));
}
return (
<main className={classes.counter}>
<h1>Redux Counter</h1>
{showState && <div className={classes.value}>{Counter}</div>}
<div>
<button onClick={increase} >
increment
</button>
<button onClick={increaseBy5} >
increment by 5
</button>
<button onClick={decrease} >
decrement
</button>
</div>
</main>
);
};
export default Counter;
Lets understand the code above.
useSelector - This is used to selete the part of state from our global state we want to use. By using this hook in the component, redux automatically subscribes that components to listen to changes and re-build in case that state changes
useDispatch - This is used to dispatch actions from the reducers we created before in the slice
Now whenever our functions (
increase, decrease, and increaseBy5
) gets called, we use our dispatch together with counterActions we exported from the counterSlice to make the reducers update the state and then since the component in listening the changes in the state, it automatically re-builds
NB: Note that increaseBy5 passes 5 to the increaseBy5 reducer, and that is the payload with which we increase our value by.
π― You can always drop a comment below π
Top comments (0)