DEV Community

Cover image for Redux-Toolkit and React-Redux For App-Wide State Management
Michael Umeokoli
Michael Umeokoli

Posted on • Edited on

Redux-Toolkit and React-Redux For App-Wide State Management

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 Hot

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

Enter fullscreen mode Exit fullscreen mode

Your inital file setup will look like this on your IDE

Initial React set-up
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..

new react setup

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;


Enter fullscreen mode Exit fullscreen mode

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>
);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

before setting up useSelector

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;
Enter fullscreen mode Exit fullscreen mode

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 }

});

Enter fullscreen mode Exit fullscreen mode

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.

after setting up useSelector

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;

Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
murashow profile image
Makar Murashov

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:

const store = configureStore({
  reducer: null,
});
Enter fullscreen mode Exit fullscreen mode

See more here

Collapse
 
mikey247 profile image
Michael Umeokoli

Hello Makar,
Thank you for your feedback, I'll get on it ASAP.