The most important part of any web application is data(state), data that changes and affects how our application works and performs, to effectively use this data we need ways to change it (useState and useReducer) and ways to access it in every part of our application where it is needed.
A little while ago I felt the need the need to learn Redux (such a cool name btw) because it is the standard for state management tools and there a lot of them (trust me I was shocked at how many are in existence). Anyways, I had previously been using the Context API hooks createContext and useContext and they did the job for me, but as your application grows the Context API can get more complex and harder to manage, there is also the issue of performance as your application gets bigger, this is where state management libraries such as Redux/Redux Toolkit come in🦸♂️.
Redux is a cross-component state management system, it aids us in managing and monitoring state without 'props drilling/chaining'(passing state through props in components that have no need for them) and Redux Toolkit is basically just the modern way of writing Redux and hence is the focus of this article.
HOW DOES REDUX TOOLKIT WORK
Redux Toolkit gives us a central data store (CDS) that handles the state needed application wide, slices of data are stored within the CDS, these slices have unique names, initial data and reducers. slice reducers are functions that alter the state of data in the slice when triggered. The CDS gives components ability to subscribe to the data in the store giving the components access to the data and the components are also notified of any changes made to the data they are subscribed to and they react to the change accordingly, components can also make changes to the store by triggering "actions" in the store through the store's reducers
For this article we will be building a simple counter app aiming at explaining the basic setup and usage of the Redux Toolkit on React apps.
Create React app
npx create-react-app counter-app
Your inital file setup will look like this on your IDE
We'll do a little cleanup of files we don't need and create two new folders in the src
folder called components
and store
, your setup should now look like this..
Installation of packages
We need to install two packages on your React app, the redux toolkit and the react-redux package, react-redux is official React bindings for Redux, react redux is a helper package maintained by the official redux team that helps you manage redux data better, it enables you to easily make connections to the redux store and dispatch actions better, it checks to see if the data your component wants has changed, and re-renders your component, in all react-redux makes life easier when using redux toolkit.
npm install @reduxjs/toolkit react-redux
OR
yarn add @reduxjs/toolkit react-redux
//this installs both packages
"npm run start" to start the application
LET'S BUILD
We'll set up our store folder this will contain our redux store, create a file called index.js
CREATING THE STORE
store/index.js
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({
reducer: null,
});
export default store;
We make use of the configureStore
function provided to us by redux toolkit and an object containing the reducer value is passed in as an argument, it creates the CDS (central data store) for us where we store our state slices in the store reducer
.
CREATING THE DATA SLICE
createSlice is another function provided to us by redux toolkit, it takes in an object with three properties name, initialState which is the state of the data when the app starts and reducers which are functions that perform some kind of logic for changing the state of the data, when we create a slice redux toolkit automatically generates "actions" object from the data slice corresponding with the reducers names and these actions are what components trigger to make state changes.
// store/index.js
import { configureStore, createSlice } from "@reduxjs/toolkit";
const initialCounterState = { count: 0 };
const counterSlice = createSlice({
name: "counter",
initialState: initialCounterState,
reducers: {
increment: (state) => {
state.count++;
},
decrement: (state) => {
state.count--;
},
},
});
const store = configureStore({
reducer: counterSlice.reducer,
});
export const counterActions = counterSlice.actions;
export default store;
In the above code we have a counterSlice with a name, initialState and two reducers one for increasing the count by 1 and one for decreasing the count by 1. The reducers in the slice here get access the value of the current state of count
which is currently 0
through the automatic state
parameter provided by redux toolkit and has the ability to change it depending on the logic inside the reducer.
We then pass in the built-in reducer property of the slice (counterSlice.reducer
) as a value to the reducer key of the configureStore object and that sets up our store. The reducer property is automatically created by redux toolkit gives the store access to the values of the slice.The data slice actions are also exported from the store so they can be used to make changes to our data from anywhere in the application.
MAKING OUR STORE GLOBALLY AVAILABLE
We need to make the store globally available to all components and for that we go to the top of our components tree index.js
in the root folder
We import the store
from store/index.js and a special component called Provider
from our helper package react-redux
this component is used to wrap App.js
it takes in a store props which we set to our imported store, this makes our store available for all our components.
// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { Provider } from "react-redux";
import store from "./store/index";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
SETTING UP OUR COMPONENTS TO MAKE USE OF REDUX
Create a file in the components folder called Counter.js
it will contain our Counter component with empty functions which we will wire up to the slice actions so we can trigger the reducers to increase/decrease the count.
const Counter = () => {
const incrementHandler = () => {};
const decrementHandler = () => {};
return (
<main>
<h1>Redux Counter</h1>
<h2>--Counter Value--</h2>
<div>
<button onClick={incrementHandler}>increase</button>
<button onClick={decrementHandler}>decrease</button>
</div>
</main>
);
};
export default Counter;
So in the Counter.js
file we will be importing 3 things, the counterActions
from store/index.js remember we exported the slice auto-generated actions earlier and two hooks from react-redux
1) useSelector
for getting access to the state of your choice and 2) useDispatch
to dispatch actions and trigger the reducer functions in your slice.
import { useSelector, useDispatch } from "react-redux";
import { counterActions } from "../store";
const Counter = () => {
//storing our dispach function into a value
const dispatch = useDispatch();
//
const count = useSelector((state) => state.count);
const incrementHandler = () => {
dispatch(counterActions.increment());
};
const decrementHandler = () => {
dispatch(counterActions.decrement());
};
return (
<main>
<h1>Redux Counter</h1>
<h1>{count}</h1>
<div>
<button onClick={incrementHandler}>increase</button>
<button onClick={decrementHandler}>decrease</button>
</div>
</main>
);
};
export default Counter;
Getting the state out of the store using useSelector hook is done by passing in a function that receives the state being managed in the store and picking the exact state we need which in this case is the count
state and if we had multiple slices and therefore multiple reducers in our store reducer for example an authentication slice..
Example:
//Single slice
const store = configureStore({
reducer: counterSlice.reducer,
});
//Multiple slices
const store = configureStore({
reducer: { counter: counterSlice.reducer, auth: authSlice.reducer }
});
you will then target the count state in the slice using this code
instead >>> const count = useSelector((state) => state.counter.count)
we have to go down one more level because the count
and auth
slices are now being stored in an object.
Our decrease and increase buttons now work and the value of the count is displayed, We can take it a little further by passing in parameters to our slice actions/reducers.
// store/index.js
import { configureStore, createSlice } from "@reduxjs/toolkit";
const initialCounterState = { count: 0 };
const counterSlice = createSlice({
name: "counter",
initialState: initialCounterState,
reducers: {
increment: (state) => {
state.count++;
},
decrement: (state) => {
state.count--;
},
increaseByValue: (state, action) => {
state.count = state.count + action.payload;
},
}
});
const store = configureStore({
reducer: counterSlice.reducer,
});
export const counterActions = counterSlice.actions;
export default store;
I've added one more function to the reducers and this function is different from the others because it takes in another parameter called "action" which is an object containing a "payload" property, this property is what wil holds whatever argument we pass into the action in our Counter component.
import { useSelector, useDispatch } from "react-redux";
import { counterActions } from "../store";
const Counter = () => {
//storing our dispach function into a value
const dispatch = useDispatch();
//
const count = useSelector((state) => state.count);
const incrementHandler = () => {
dispatch(counterActions.increment());
};
const decrementHandler = () => {
dispatch(counterActions.decrement());
};
const increaseByValueHandler = () => {
dispatch(counterActions.increaseByValue(5));
};
return (
<main>
<h1>Redux Counter</h1>
<h1>{count}</h1>
<div>
<button onClick={incrementHandler}>increase</button>
<button onClick={decrementHandler}>decrease</button>
<button onClick={increaseByValueHandler}>increase by 5</button>
</div>
</main>
);
};
export default Counter;
AND WE'RE DONE!
You can tweak the increaseByValue function to receive it's argument dynamically using useState or useRef to get the value of an input field but this is as far as I'll take it.
Of course redux-toolkit/react-redux are mainly used for more complicated state logic and across much more components but the purpose of this article was just to demonstrate how redux works and as you can see it's not that hard to set up.
See you next time. Mikey out✌🏾
Top comments (2)
Great stuff, Mikey, thank you!
One suggestion: when you put a
Code block
in your article you can write a language name there after 3 backtikcs. so the code will be highlighted:javascript
:See more here
Hello Makar,
Thank you for your feedback, I'll get on it ASAP.