Simplify Your Redux States with Redux Toolkit
Tags: react, redux, webdev, javascript
Published: 2023-06-27 03:59:22

We already know what redux is and what problems it solves. But redux has its own problems like lots of configurations, need to create reducers and action creators separately, etc...
Now with redux toolkit we don't have that problem anymore. It simplifies our code, provides a standard structure, and comes with powerful tools like createSlice and createAsyncThunk, that can help you create and manage slices of state.

## A simple app to play around with redux toolkit
Before we start you can clone this repo:

```bash
git clone https://github.com/duniandewon/robofriends-pwa.git
```
I know it has pwa in it, but we are not making progressive web apps now. Maybe some other time.

This is a simple app where we fetch some users data from [jsonplaceholder](https://jsonplaceholder.typicode.com/) and filter them by their name.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6r1cl5245m8tthl6y5hf.png)

Now first thing we need to do is download react-redux and @reduxjs/toolkit 

```bash
npm install react-redux @reduxjs/toolkit
```
Remember how many packages we had to install before? Now we only need these two...

Create a file and name it store.ts which will contain:

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

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

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

```
To create a store we use configureStore function from redux toolkit and all future reducers will go into the reducer object.

The last two variables we export are typescript stuff for type safety.
Now we go to **app.tsx** and wrap our app with redux provider

```jsx
import { Provider } from "react-redux";
import { store } from "@/redux/store";

// other imports...

function App() {
  // other stuff may going on here...

  return (
    <Provider store={store}>
      // other components...
    </Provider>
  );
}

export default App;
```
Now for our reducers create a file and name it **robotsSlice.ts**
We are not slicing robots here, we are creating a slice of our redux state that looks like this:

```js
import { PayloadAction, createAsyncThunk, createSlice } from "@reduxjs/toolkit";

import { RootState } from "./store";

interface InitialState {
  robots: IRobot[];
  filteredRobots: IRobot[];
  status: "idle" | "loading" | "failed";
}

const initialState: InitialState = {
  robots: [],
  filteredRobots: [],
  status: "idle",
};

export const fetchRobots = createAsyncThunk("robots/fetch", async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/users");
  const robots = await res.json();

  return robots;
});

const robotsSlice = createSlice({
  name: "robots",
  initialState,
  reducers: {
    filterRobots: (state, action: PayloadAction<string>) => {
       // filtering robots...
   },
  extraReducers: (builder) => {
    builder
      .addCase(fetchRobots.pending, (state) => {
        state.status = "loading";
      })
      .addCase(fetchRobots.fulfilled, (state, action) => {
        state.status = "idle";
        state.robots = action.payload;
      })
      .addCase(fetchRobots.rejected, (state) => {
        state.status = "failed";
      });
  },
 }
});

export const selectRobots = (state: RootState) => state.robots.robots;
export const selectFilteredRobots = (state: RootState) =>
  state.robots.filteredRobots;

export const { filterRobots } = robotsSlice.actions;
export default robotsSlice.reducer;
```
This is our whole action types, action creators, reducers. Much cleaner right?
To create our state we mainly use **createSlice** function. It has three required properties: name, initialState, and reducers.
Name and initialState properties are self-explanatory. The reducers property is where our **synchronous** action creators go.
To create an asynchronous action we use **createAsyncThunk** that takes a string and a callback function where we can do all sort of async stuff. But to let redux know about our async action we need to configure it in **extraReducers**.
Now let's go back to out **store.ts** and put our newly created reducer in there
```js
// other imports...

import robotsReducer from "@/redux/robotsSlice";

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

// other stuff

```
By the way... I created custom hook for useDispatch and useSelector to have more type safety:

```js
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";

import type { RootState, AppDispatch } from "@/redux/store";

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
```
Now we can use our actions in peace
**RobotsList.tsx:**

```jsx
import { useCallback, useEffect } from "react";

import RobotItem from "./RobotItem";

import { useAppDispatch, useAppSelector } from "@/hooks/useDispatch";
import {
  fetchRobots,
  selectFilteredRobots,
  selectRobots,
} from "@/redux/robotsSlice";

function RobotsList() {
  const dispatch = useAppDispatch();

  const robots = useAppSelector(selectRobots);
  const filteredRobots = useAppSelector(selectFilteredRobots);

  const renderRobots = useCallback(() => {
    const data = filteredRobots.length ? filteredRobots : robots;

    return data.map((robot) => <RobotItem robot={robot} key={robot.id} />);
  }, [filteredRobots, robots]);

  useEffect(() => {
    dispatch(fetchRobots());
  }, [dispatch]);

  return (
    <ul className="flex flex-wrap gap-4 px-3 justify-center">
      {renderRobots()}
    </ul>
  );
}
```
**SearchBox.tsx:**

```jsx
import { ChangeEvent, useCallback, useEffect, useRef, useState } from "react";

import { filterRobots } from "@/redux/robotsSlice";

import { useAppDispatch } from "@/hooks/useDispatch";

function SearchBox() {
  const [search, setSearch] = useState("");
  const ref = useRef<HTMLInputElement>(null);

  const dispatch = useAppDispatch();

  const handleChange = useCallback(
    (e: ChangeEvent<HTMLInputElement>) => {
      setSearch(e.target.value);
      dispatch(filterRobots(e.target.value));
    },
    [dispatch]
  );

  useEffect(() => {
    ref.current?.focus();
  }, []);

  return (
    <div className="max-w-lg mx-auto mt-6 rounded-lg">
      <label htmlFor="search" className="sr-only">
        Search robots
      </label>
      <input
        type="text"
        id="search"
        className="outline-slate-500 border-2 border-slate-400 py-4 pl-4 w-full rounded-lg"
        placeholder="Search robots"
        ref={ref}
        value={search}
        onChange={handleChange}
      />
    </div>
  );
}

export default SearchBox;
```
That's it. You can checkout the entire app in this [repo](https://github.com/duniandewon/robofriends-pwa/tree/with-redux)