Redux Toolkit for Beginners

A brief intro to Redux

Redux is a state management library for JavaScript applications, offering a centralized store to manage application state. It follows principles of having a single source of truth, immutable state, and state changes managed by pure functions called reducers. By dispatching actions to update the state, Redux ensures predictability and maintainability in complex applications, making it a valuable tool for developers working with frameworks like React or other JavaScript libraries.

Lets's dive into Coding

Welcome to this beginner's guide to Redux! Over 8 clear steps, we'll cover everything from setting up Redux to integrating it with React.

Check out this github repo if you get stuck anywhere: here

Step 1 : Setting up React and Tailwind Css

I am not going to discuss how to set up react and tailwind in this blog. However you can check out my previous blog to learn that.

To be on the same page, you can check out the Initial Commit of the github repo.

Step 2 : Setting up Redux

We need to install redux toolkit along with a package react-redux.

npm install @reduxjs/toolkit react-redux

Next we need to create a store.ts file.

// src/redux/store.ts

import { configureStore } from "@reduxjs/toolkit";

export const store = configureStore({
  reducer: {},
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

Since we have not created any reducers as of now so the reducer field will be an empty object.

We will also learn what a reducer is when we create one.

Next we will wrap our entire app with the Provider component so that any component from the component tree can have access to the state directly. We will do this in the main.tsx file.

// main.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { Provider } from "react-redux";
import { store } from "./redux/store.ts";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

Step 3 : Create the first Redux Slice

So what is a Slice in Redux ?

In Redux, a "slice" refers to a portion of your application's state and the corresponding set of reducer functions that manage that particular slice of state. Slices help organize your Redux store into smaller, more manageable pieces, making it easier to understand and maintain your application's state logic. Eg if you were creating a school app then you could create different slices for teachers and students which would maintain the state separately.

In this blog, we will focus on creating a single slice of state. However, it's important to note that in Redux, multiple slices can be created as needed.

// src/redux/slices/counterSlice.ts

import { createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'

export interface CounterState {
  value: number
}

const initialState: CounterState = {
  value: 0,
}

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload
    },
    decrementByAmount: (state, action: PayloadAction<number>) => {
      state.value -= action.payload;
    },
  },
})

export const { increment, decrement, incrementByAmount, decrementByAmount } = counterSlice.actions

export default counterSlice.reducer

So here comes the most important part of this blog.

Reducer - In essence, a reducer is a pure function responsible for managing state changes in response to dispatched actions. It takes in the current state and an action as arguments, and returns a new state based on the action type. Think of it as the brain behind state modifications: it determines what changes need to be made to the state based on the action it receives.

Action - An action is a simple JavaScript object that contains information about an event that occurred in the application. It typically consists of two main parts: a type or name, which determines which reducer function should be called to update the state, and a payload, which carries any additional data needed to make those state changes. Think of the action type as the identifier for the kind of change you want to make, and the payload as the data necessary to execute that change. We do not need to think about the name of the action because that is handled by the toolkit itself.

Now we need to add the reducer of the counterSlice in the store.ts

// src/redux/store.ts

import { configureStore } from "@reduxjs/toolkit";
import counterSlice from "./slices/counterSlice";

export const store = configureStore({
  reducer: {
    counter: counterSlice,
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

We have successfully setup the reducers. The only thing that is left is to call these reducer functions as response to clicking or changes made by the user. And we will use the actions exported from the counterSlice to do that.

Step 4 : Creating the UI of the application

Here we're focusing solely on creating the user interface without any functional behavior attached to it. So, even if you interact with the increment or decrement buttons, nothing will happen in terms of updating the state or performing any actions.

// App.tsx

const App = () => {
  return (
    <div className="flex items-center justify-center h-screen">
      <div className="bg-slate-100 w-64 h-64 flex flex-col items-center p-4 rounded-lg border-2 border-slate-300">
        <h1 className="text-xl font-semibold">Count</h1>
        <p className="text-7xl font-semibold mt-3">0</p>
        <div className="mt-auto w-full flex items-center gap-x-4">
          <button className="btn">+</button>
          <button className="btn">-</button>
        </div>
      </div>
    </div>
  );
};
export default App;
// index.css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  .btn {
    @apply bg-slate-200 flex-1 text-4xl font-semibold text-slate-800 flex items-center justify-center rounded-lg pb-2;
  }
}

Step 5 : Using actions to update state

We have already exported the actions from the counterSlice file.

Now we are going to use 2 hooks from react-redux package:

useSelector - this hook enables us to extract and retrieve specific pieces of state from the Redux store, typically from a particular slice. Here we have used it to get the value of count.

useDispatch - this hook is used to interact with the Redux store by dispatching actions, which in turn trigger the corresponding reducer functions.

// App.tsx

import { useSelector, useDispatch } from "react-redux";
import {
  increment,
  decrement,
  incrementByAmount,
  decrementByAmount,
} from "./redux/slices/counterSlice";
import { RootState } from "./redux/store";

const App = () => {
  const count = useSelector((state: RootState) => state.counter.value);
  const dispatch = useDispatch();
  return (
    <div className="flex items-center justify-center h-screen">
      <div className="bg-slate-100 w-64 h-64 flex flex-col items-center p-4 rounded-lg border-2 border-slate-300">
        <h1 className="text-xl font-semibold">Count</h1>
        <p className="text-7xl font-semibold mt-3">{count}</p>
        <div className="mt-auto w-full flex items-center gap-x-4">
          <button className="btn" onClick={() => dispatch(increment())}>
            +
          </button>
          <button className="btn" onClick={() => dispatch(decrement())}>
            -
          </button>
        </div>
        <div className="mt-auto w-full flex items-center gap-x-4">
          <button
            className="btn"
            onClick={() => dispatch(incrementByAmount(10))}
          >
            +10
          </button>
          <button
            className="btn"
            onClick={() => dispatch(decrementByAmount(10))}
          >
            -10
          </button>
        </div>
      </div>
    </div>
  );
};
export default App;

That's it for this project.

Conclusion

In this blog, we've embarked on a journey through the fundamentals of Redux. From laying the groundwork with Redux store setup and defining reducers to integrating Redux with React using hooks like useSelector and useDispatch, we've see the core concepts of redux. As you venture forth, you can delve deeper into advanced Redux concepts, such as middleware and asynchronous actions.

Check out redux-toolkit documentation to learn more: here

Happy Coding!