A Practical Introduction to Using Redux with React
Tags: react, redux
Published: 2020-08-04 10:20:28

## Table of Contents
1. [Introduction](#introduction)
2. [What is Redux?](#what-is-redux)
3. [What is the State?](#what-is-the-state)
4. [How to Modify the State?](#how-to-modify-the-state)
5. [Unidirectional Data Flow](#unidirectional-data-flow)
6. [Setting Up Redux in a React App](#setting-up-redux-in-a-react-app)
7. [Using React Hooks to Read the State](#using-react-hooks-to-read-the-state)
8. [Using React Hooks to Dispatch Actions](#using-react-hooks-to-dispatch-actions)
9. [Using "json-server" for the Local Fake API](#using-json-server-for-the-local-fake-api)
10. [Async Actions](#async-actions)
11. [Multiple Reducers](#multiple-reducers)
12. [Feature Folders and Ducks](#feature-folders-and-ducks)
13. [Using the "Ducks" Pattern in Our Example App](#using-the-ducks-pattern-in-our-example-app)
14. [Using Redux Toolkit (Recommended)](#using-redux-toolkit-recommended)

## Introduction

In this tutorial I'd like to explain briefly what is Redux and how to set it up in a React project.

This tutorial would be useful for you if you've learned React already and would like to learn how Redux can help managing the app's state globally.

Many of us have heard that the Redux code we have to write using original Redux API is a little verbose (e.g., initializing store, creating actions and reducers, etc). Therefore, the team behind Redux created a package called _Redux Toolkit_ which makes developing Redux apps easier and more fun. Moreover, using _Redux Toolkit_ for writing Redux logic is official recommended approach.

In this tutorial we'll start with the basics, then we'll build a simple app using just Redux (without the toolkit). Finally, we'll add Redux Toolkit to improve our Redux code.

So, let's get started.

## What is Redux?

Redux is a state management library. Commonly, it is used together with React, but it can be used with other view libraries too.
Redux helps us to keep the state of the whole app in a single place.

## What is the State?

I would describe "state" as the data that is used to render the app at any given time. We keep this data in a JavaScript object. For example, in a simple app which renders a list of muffins, the state could look like this:

```javascript
let state = {
  muffins: [
    { name: 'Chocolate chip muffin' },
    { name: 'Blueberry muffin' }
  ]
}
```

## How to Modify the State?

To modify the state from within a component we dispatch an action:

```javascript
// SomeComponent.js
dispatch({
  type: 'muffins/add',
  payload: {
    muffin: { name: 'Banana muffin' },
  },
});
```

Dispatching actions is the only way to change the state.

An action is represented by an object with the `type` property. The `type` property is the action's name. You can add any other property to this object (this is how you pass the data to reducer).

There are no formal rules as to how you should name your actions. Give your actions descriptive and meaningful names. Don't use ambiguous names, like `receive_data` or `set_value`.

It is a common practice to share actions through the action creator functions. Such functions create and return the action objects. We store action creators outside of the component files (e.g., _src/redux/actions.js_). This makes it easy to see what actions are available in the app and to maintain and reuse them.

```javascript
// actions.js
export function addMuffin(muffin) {
  return {
    type: 'muffins/add',
    payload: { muffin },
  };
}

// SomeComponent.js
dispatch(addMuffin({ name: 'Banana muffin' }));
```

Once an action is dispatched, Redux calls the reducer with the previous state and the dispatched action object as the arguments. Reducer is a function which decides how to change the state according to a given action. We create this function and register it with Redux.

This is how a basic reducer looks like:

```javascript
let initialState = {
  muffins: [
    { id: 1, name: 'Chocolate chip muffin' },
    { id: 2, name: 'Blueberry muffin' },
  ],
};

function reducer(state = initialState, action) {
  switch (action.type) {
    case 'muffins/add':
      let { muffin } = action.payload;
      return { ...state, muffins: [...state.muffins, muffin] };
    default:
      return state;
  }
}
```

When this reducer identifies the `muffins/add` action it adds the given muffin to the list.

**IMPORTANT.** The reducer copies the previous state object instead of mutating it. The rule is that the state must be immutable (read-only). The reducer should copy any object that it would like to change before changing it. This includes the root object and any nested objects.

We need to copy the state for Redux to be able to check (using shallow checking) if the state returned by the reducer is different from the previous state. Check this for more details about shallow checking: [How do shallow and deep equality checking differ?](https://redux.js.org/faq/immutable-data/#how-do-shallow-and-deep-equality-checking-differ). It is important to follow this rule for Redux to respond to our state changes correctly. Also, when using redux with react-redux this helps react-redux to decide which components should be re-rendered when the state changes.

The other important rule is that the reducer function should be pure. Given the same input it should always produce the same output without causing any side effects. A side affect is something that reads or changes the environment around the function. Examples of side effects are reading or writing a global variable, running a network request, etc. This rule helps us reproduce the look and behavior of the app given a particular state object.

Also, both of these rules make sure that Redux's time travel feature works properly with our app. Time travel allows us to easily undo actions and then apply them again. This helps a lot with debugging using [Redux DevTools](https://github.com/reduxjs/redux-devtools).

To summarize:

- Our app has a single state.
- To change this state we dispatch actions.
- The reducer function handles the dispatched actions and changes the state accordingly.
- Redux and react-redux check the state returned by the reducer for changes using _shallow checking_.

## Unidirectional Data Flow

So, we've learned the following about Redux: we dispatch an action from the view layer (e.g., a React component), reducer gets this action and changes the state accordingly, the store notifies the view layer about the state change and the view layer renders the app according to the latest state. And the cycle repeats when we need to change the state again.

So, the data in a Redux app flows in a single way circular pattern. It is also called a unidirectional data flow. This is how we could represent it using a diagram:

![redux-data-flow](https://dev-to-uploads.s3.amazonaws.com/i/ge557nuigp2nwy29kj3a.png 'Redux data flow')

This pattern makes it easier to understand how a Redux app works.

## Setting Up Redux in a React App

In this post we will be building a simple app which lists a number of muffins.

I've initialized a basic React app using [create-react-app](https://create-react-app.dev/):

```bash
npx create-react-app my-react-redux
```

I removed extra code and rendered a hard-coded list of muffins. This is what I've got: [View on GitHub](https://github.com/d-dmytro/my-react-redux/tree/088ff643896ae1fbcbc55889369c8d30c53d5306)

Let's go ahead and store the muffins in the state.

First, let's install "redux" and "react-redux" packages:

```bash
npm i -S redux react-redux
```

Remember, Redux can be used with other view libraries. So we need the "react-redux" package to connect React components with Redux store.

Next, we should prepare the Redux store. The store is an object which keeps the app's state and provides the API for working with it. It allows us to:

- read the state
- dispatch actions to change the state
- and subscribe/unsubscribe to/from the state changes

**IMPORTANT.** Your app should have a single store.

Let's go ahead and set up the store for our example app.

Let's keep the Redux functionality in the folder called "redux":

```bash
mkdir src/redux
```

Let's write the store initialization code in the file _src/redux/store.js_:

```javascript
// File: src/redux/store.js
import { createStore } from 'redux';

const initialState = {
  muffins: [
    { id: 1, name: 'Chocolate chip muffin' },
    { id: 2, name: 'Blueberry muffin' },
  ],
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    default:
      return state;
  }
};

const store = createStore(reducer);

export default store;
```

We use the `createStore` function from the `redux` package to create the store. When the store initializes, it obtains the initial state by calling our reducer function with `undefined` for the state and a dummy action (e.g., `reducer(undefined, { type: 'DUMMY' })`).

Now we should provide the store to the React components.
For this, we open the _src/index.js_ and wrap the `<App />` component into the `<Provider />` component from the "react-redux" package:

```javascript
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import './index.css';
import App from './components/App';
import store from './redux/store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
```

The `<Provider />` component provides the store to the child component tree using React context. Now we can use the React hooks or the `connect` function from the "react-redux" package to get the state and dispatch actions from any component in the tree.

[View the code on GitHub](https://github.com/d-dmytro/my-react-redux/commit/d2f49f975bb9a8dbbdfbabed39b27fb70a2d31f0)

## Using React Hooks to Read the State

Instead of hard-coding the muffin list in the "Muffins.js", let's use the `useSelector` hook from "react-redux" to select the muffins array from the state.

```javascript
// file: src/redux/selectors.js
export const selectMuffinsArray = (state) => state.muffins;
```

```javascript
// file: src/components/Muffins/Muffins.js
import React from 'react';
import { useSelector } from 'react-redux';
import { selectMuffinsArray } from '../../redux/selectors';

const Muffins = () => {
  const muffins = useSelector(selectMuffinsArray);

  return (
    <ul>
      {muffins.map((muffin) => {
        return <li key={muffin.id}>{muffin.name}</li>;
      })}
    </ul>
  );
};

export default Muffins;
```

The `useSelector` hook expects a selector function as the first argument. We create selector functions to provide a reusable API for selecting different parts of the state.

We use the state in many components. If we select things from the state directly (e.g. `let muffins = state.muffins`) and at some point we change the structure of the state (e.g., `state.muffins` becomes `state.muffins.items`) we'd have to edit each component where we access the state properties directly. Using selector functions we can change the way we select the state in a single place (in our example, it is the "selectors.js" file).

[View the code on GitHub](https://github.com/d-dmytro/my-react-redux/commit/249711ae8fd839f6f982ff9715f23ee533e29b7e)

## Using React Hooks to Dispatch Actions

Let's add a "Like" button to each muffin in the list.

First, let's add the "likes" property to the state (number of likes).

```javascript
// file: src/redux/store.js
const initialState = {
  muffins: [
    { id: 1, name: 'Chocolate chip muffin', likes: 11 },
    { id: 2, name: 'Blueberry muffin', likes: 10 },
  ],
};
```

Next, let's render the number of likes and the "Like" button.

```javascript
// file: src/components/Muffins/Muffins.js
<li key={muffin.id}>
  {muffin.name} <button>Like</button> <i>{muffin.likes}</i>
</li>
```

Now, let's get the `dispatch` function in the component using the `useDispatch` hook from "react-redux".

```javascript
// file: src/components/Muffins/Muffins.js
import { useSelector, useDispatch } from 'react-redux';
// ...
const dispatch = useDispatch();
```

Let's define an action for the "Like" button.

```javascript
// File: src/redux/actions.js
export const likeMuffin = (muffinId) => ({
  type: 'muffins/like',
  payload: { id: muffinId },
});
```

Next, let's create the "click" event handler for the "Like" button:

```javascript
// {"lines": "2,4-9,12"}
// file: src/components/Muffins/Muffins.js
import { likeMuffin } from '../../redux/actions';

// ...

{
  muffins.map((muffin) => {
    const handleLike = () => {
      dispatch(likeMuffin(muffin.id));
    };
    return (
      <li key={muffin.id}>
        {muffin.name} <button onClick={handleLike}>Like</button>{' '}
        <i>{muffin.likes}</i>
      </li>
    );
  });
}
```

If we click this button, nothing happens, because we didn't create a reducer for the action that is dispatched (`muffins/like`).

So, let's go ahead and reduce this action.

```javascript
// {"lines": "4-14"}
// file: src/redux/store.js
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'muffins/like':
      const { id } = action.payload;
      return {
        ...state,
        muffins: state.muffins.map((muffin) => {
          if (muffin.id === id) {
            return { ...muffin, likes: muffin.likes + 1 };
          }
          return muffin;
        }),
      };
    default:
      return state;
  }
};
```

It's important NOT to mutate the state. So, I copy the state object, copy the muffins array (the map method returns a new array). Finally, I copy only the muffin which is being changed. I don't touch the other muffins to signify that they do not change.

Now, if we click the "Like" button, the `muffins/like` action is dispatched and the reducer changes the state accordingly. The number of likes of the chosen muffin increments.

[View the code on GitHub](https://github.com/d-dmytro/my-react-redux/commit/6bb118769537fcc48cc2442c1b00a17e0aa5e6c2)

## Using "json-server" for the Local Fake API

"json-server" is a fake REST API server which is really easy to set up. We can use it to mock API endpoints while working on a front end app. I'd like to use this server for the examples in this post. So let me show you how to install and run it.

To install:

```bash
npm i -D json-server
```

To tell the server what data it should serve we create a JSON file. Let's call it _db.json_.

```json
{
  "muffins": [
    { "id": 1, "name": "Chocolate chip muffin", "likes": 11 },
    { "id": 2, "name": "Blueberry muffin", "likes": 10 }
  ]
}
```

Now let's open _package.json_ and add the script which will start this server:

```json
// {"lines": "2"}
"scripts": {
  "json-server": "json-server --watch db.json --port 3001"
}
```

To run it:

```bash
npm run json-server
```

The server should start on [http://localhost:3001](http://localhost:3001).

To stop it, focus on the terminal window where you started it and press `CTRL + C`.

We can use the following routes ("json-server" generates them by looking at _db.json_)

```
GET /muffins
POST /muffins
PUT /muffins/{id}
DELETE /muffins/{id}
```

[View the code on GitHub](https://github.com/d-dmytro/my-react-redux/commit/207381e610adc44b20ec6e3313872d3c9c468387)

## Async Actions

Please check the section _Using "json-server" for the Local Fake API_.

Usually, we run network requests to get and edit the data. Let's see how to do it the Redux way.

By default, Redux allows us to dispatch an action only in the form of an object with the `type` property.

However, Redux allows us to alter the way it dispatches actions using a middleware function. One such function is called "redux-thunk".

Let's install and register this middleware function with Redux.

```bash
npm i -S redux-thunk
```

```javascript
// file: src/redux/store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
// ...
const store = createStore(reducer, applyMiddleware(thunk));
```

`applyMiddleware` is a utility function which takes a list of middleware functions and groups them in a single middleware function which we pass to `createStore` as the second argument.

Also, let's empty the muffins array in the initial state, because we are going to load muffins from the fake API.

```javascript
// file: src/redux/store.js
const initialState = {
  muffins: [],
};
```

"redux-thunk" allows us to dispatch not only objects, but also functions:

```javascript
dispatch((dispatch, getState) => {
  let state = getState();
  // do something async and
  dispatch(/* some action */);
});
```

The thunk function gets the original `dispatch` function as the first argument and the `getState` function as the second argument.

So, what we can do with a thunk function is, for example, to fetch the data from the network and when the data is ready we can dispatch an action object with this data, so reducer can add this data to the state.

Let's create the _actions.js_ file and add the async action creator function for loading muffins.

```javascript
// file: src/redux/actions.js
export const loadMuffins = () => async (dispatch) => {
  dispatch({
    type: 'muffins/load_request',
  });

  try {
    const response = await fetch('http://localhost:3001/muffins');
    const data = await response.json();

    dispatch({
      type: 'muffins/load_success',
      payload: {
        muffins: data,
      },
    });
  } catch (e) {
    dispatch({
      type: 'muffins/load_failure',
      error: 'Failed to load muffins.',
    });
  }
};
```

A thunk function can be either sync or async. We can dispatch multiple actions in this function. In our example we dispatch the `muffins/load_request` action to signify that the request starts. We can use this action to show a spinner somewhere in the app. Then, when the request succeeds we dispatch the `muffins/load_success` action with the fetched data. Finally, if the request fails, we dispatch the `muffins/load_failure` action to show the error message to the user.

Now, let's create the reducers for these actions.

```javascript
// file: src/redux/store.js
const reducer = (state = initialState, action) => {
  switch (action.type) {
    // ...
    case 'muffins/load_request':
      return { ...state, muffinsLoading: true };

    case 'muffins/load_success':
      const { muffins } = action.payload;
      return { ...state, muffinsLoading: false, muffins };

    case 'muffins/load_failure':
      const { error } = action;
      return { ...state, muffinsLoading: false, error };
    // ...
  }
};
```

Let's dispatch the `loadMuffins` action in the `Muffins` component, when it mounts.

```javascript
// file: src/components/Muffins/Muffins.js
import React, { useEffect } from 'react';
import { loadMuffins } from '../../redux/actions';

// ...

const dispatch = useDispatch();

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

We are loading muffins in the effect hook, because dispatching an action is a side effect.

Finally, let's handle the loading and error states.

Create the following selector functions:

```javascript
// file: src/redux/selectors.js
export const selectMuffinsLoading = (state) => state.muffinsLoading;
export const selectMuffinsLoadError = (state) => state.error;
```

And render the loading and error messages:

```javascript
// file: src/components/Muffins/Muffins.js
const muffinsLoading = useSelector(selectMuffinsLoading);
const loadError = useSelector(selectMuffinsLoadError);

// ...

return muffinsLoading ? (
  <p>Loading...</p>
) : loadError ? (
  <p>{loadError}</p>
) : muffins.length ? (
  <ul>
    {muffins.map((muffin) => {
      // ...
    })}
  </ul>
) : (
  <p>Oh no! Muffins have finished!</p>
);
```

Now, let's check if we did everything correctly.

We should run the local "json-server" and the app.

In one terminal window:

```bash
npm run json-server
```

And in the other:

```bash
npm start
```

In the browser you should see the list of muffins which is, now, fetched from the fake API server.

[View the code on GitHub](https://github.com/d-dmytro/my-react-redux/commit/26eceddaa7390ef3d9fa0844b63f77becf65ef6a)

## Multiple Reducers

Usually, in a large app, state won't be that simple. It will look like a huge tree of data.

The reducer function will become bloated.

So, it's a good idea to split the reducer into multiple smaller reducers where each reducer handles only a part of the state.

For example, in order to handle the state from the picture above, it would be a good idea to create 3 reducers:

```javascript
const muffinsReducer = (state = initialMuffinsState, action) => {
  // ...
};
const notificationsReducer = (state = initialNotificationsState, action) => {
  // ...
};
const cartReducer = (state = initialCartState, action) => {
  // ...
};
```

and combine them using the utility function called `combineReducers`:

```javascript
const rootReducer = combineReducers({
  muffins: muffinsReducer,
  notifications: notificationsReducer,
  cart: cartReducer,
});

const store = createStore(rootReducer);
```

`combineReducers` creates a root reducer function which calls each sub reducer when the action is dispatched and combines the parts of the state they return into a single state object:

```
{
  muffins: ...,
  notifications: ...,
  cart: ...
}
```

Combining reducers makes it easy to modularize the reducer logic.

## Feature Folders and Ducks

The Redux documentation recommends [structuring Redux functionality as feature folders or ducks](https://redux.js.org/style-guide/style-guide#structure-files-as-feature-folders-or-ducks).

### Feature Folders

Instead of grouping all actions and reducers by the type of code (for example, all the app's actions in actions.js and all reducers in reducers.js), we could group them by feature.

Let's say there are two features: "users" and "notifications". We could keep their actions and reducers in separate folders. For example:

```
redux/
  users/
    actions.js
    reducers.js
  notifications/
    actions.js
    reducers.js
  store.js
```

### Ducks

The "ducks" pattern says that we should keep all Redux logic (actions, reducers, selectors) for a specific feature in its own file. For example:

```
redux/
  users.js
  notifications.js
  store.js
```

## Using the "Ducks" Pattern in Our Example App

In the app we've got different Redux functionality around muffins. We can group this functionality into a duck. In other words, let's just move everything related to mufffins into a JavaScript file and call it _src/redux/muffins.js_.

Let's move the actions, selectors and the reducer to this file:

```javascript
export const likeMuffin = (muffinId) => ({
  type: 'muffins/like',
  payload: { id: muffinId },
});

export const loadMuffins = () => async (dispatch) => {
  dispatch({
    type: 'muffins/load_request',
  });

  try {
    const response = await fetch('http://localhost:3001/muffins');
    const data = await response.json();

    dispatch({
      type: 'muffins/load_success',
      payload: {
        muffins: data,
      },
    });
  } catch (e) {
    dispatch({
      type: 'muffins/load_failure',
      error: 'Failed to load muffins.',
    });
  }
};

export const selectMuffinsArray = (state) => state.muffins;
export const selectMuffinsLoading = (state) => state.muffinsLoading;
export const selectMuffinsLoadError = (state) => state.error;

const initialState = {
  muffins: [],
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'muffins/like':
      const { id } = action.payload;
      return {
        ...state,
        muffins: state.muffins.map((muffin) => {
          if (muffin.id === id) {
            return { ...muffin, likes: muffin.likes + 1 };
          }
          return muffin;
        }),
      };

    case 'muffins/load_request':
      return { ...state, muffinsLoading: true };

    case 'muffins/load_success':
      const { muffins } = action.payload;
      return { ...state, muffinsLoading: false, muffins };

    case 'muffins/load_failure':
      const { error } = action;
      return { ...state, muffinsLoading: false, error };

    default:
      return state;
  }
};

export default reducer;
```

Now, in the _src/redux/store.js_, let's create the root reducer using the `combineReducers` function:

```javascript
// {"lines": "6-10"}
// File: src/redux/store.js
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import muffinsReducer from './muffins';

const rootReducer = combineReducers({
  muffins: muffinsReducer,
});

const store = createStore(rootReducer, applyMiddleware(thunk));

export default store;
```

Now, the app's state looks like this:

```
{
  muffins: {
    muffins: [],
    muffinsLoading: boolean,
    error: string
  }
}
```

Since the structure of the state has changed, to make the app work, we should update the parts of the code where we read the state. Luckily, we use selector functions to select parts of the state object instead of working with the state object directly. So, we only have to update the selector functions:

```javascript
// File: src/redux/muffins.js
export const selectMuffinsState = (rootState) => rootState.muffins;

export const selectMuffinsArray = (rootState) =>
  selectMuffinsState(rootState).muffins;

export const selectMuffinsLoading = (rootState) =>
  selectMuffinsState(rootState).muffinsLoading;

export const selectMuffinsLoadError = (rootState) =>
  selectMuffinsState(rootState).error;
```

Finally, let's update the import statements:

```javascript
// {"lines": "6,7"}
// File: src/components/Muffins/Muffins.js
import {
  selectMuffinsArray,
  selectMuffinsLoading,
  selectMuffinsLoadError,
} from '../../redux/muffins';
import { likeMuffin, loadMuffins } from '../../redux/muffins';
```

That's it! We used the "ducks" pattern to move the Redux functionality around managing the muffins state into a single file.

[View the code on GitHub](https://github.com/d-dmytro/my-react-redux/commit/c1911ef143f75daf97bc3a1b33acc5194a564f34)

## Using Redux Toolkit (Recommended)

The Redux team recommends to use the [Redux Toolkit](https://redux-toolkit.js.org/) for writing Redux logic. This toolkit contains a set of utilities that make it easier to write Redux apps. Pure Redux is a little verbose, so this toolkit wraps the complex code you had to write using pure Redux in utilities that help you writing less code. Also, it includes additional libraries that are commonly used with Redux.

Let's improve our Redux code using Redux Toolkit.

The toolkit is distributed as a separate package. Let's install it:

```bash
npm i -S @reduxjs/toolkit
```

Then, let's open _src/redux/store.js_ and update it to initialize the store using Redux Toolkit.

```js
// src/redux/store.js
import { configureStore } from '@reduxjs/toolkit';
import muffinsReducer from './muffins';

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

export default store;
```

We replaced `createStore`, `applyMiddleware`, `combineReducers`, and `redux-thunk` with a single function, `configureStore`. This function wraps the Redux `createStore`, adds default configuration and provides additional functionality for configuring the store.

`configureStore` applies the thunk middleware by default, so we don't have to set it up manually and there is no need to install the `redux-thunk` package as well. Also, this function combines reducers for us, so we no longer need Redux `combineReducers`. We add the reducers for handling different parts of the state to the `reducer` object.

To learn more about `configureStore` please [visit it's documentation](https://redux-toolkit.js.org/api/configureStore).

Redux Toolkit includes wonderful functionality for helping us with creating reducers. There is a function called `createReducer(initialState, caseReducers)`. The first argument is the initial state and the second argument is an object that maps action types to reducer functions that handle that actions.

Let's go ahead and use `createReducer` to create our reducer. In _src/redux/muffins.js_ please replace the old reducer code with the new:

```js
import { createReducer } from '@reduxjs/toolkit';

// ...

const reducer = createReducer(initialState, {
  'muffins/like': (state, action) => {
    const { id } = action.payload;

    return {
      ...state,
      muffins: state.muffins.map((muffin) => {
        if (muffin.id === id) {
          return { ...muffin, likes: muffin.likes + 1 };
        }
        return muffin;
      }),
    };
  },

  'muffins/load_request': (state) => {
    return { ...state, muffinsLoading: true };
  },

  'muffins/load_success': (state, action) => {
    const { muffins } = action.payload;
    return { ...state, muffinsLoading: false, muffins };
  },

  'muffins/load_failure': (state, action) => {
    const { error } = action;
    return { ...state, muffinsLoading: false, error };
  },
});
```

This looks better already, it's more declarative and each action is handled by its own reducer function compared to the `switch` statement where the scope is shared between `case`'s.

We shouldn't stop here, we can improve this reducer even further with the help of `createReducer`.

Earlier in this post I've told that when changing the state a reducer function should not mutate the previous state. That's why in our reducer we always return a new state object and copy the parts of the state we're changing, creating new references for Redux to be able to quickly compare previous state with the new state to find out if the state changed.

In the `createReducer` function we no longer need to copy the state object, we can mutate it directly. This function applies [Immer](https://immerjs.github.io/immer/docs/introduction) to turn our mutation to an immutable update. Let's turn our difficult to read immutable state update code to the mutable easy to read version which will be handled by Immer behind the scenes to make it immutable:

```js
const reducer = createReducer(initialState, {
  'muffins/like': (state, action) => {
    const muffinToLike = state.muffins.find(
      (muffin) => muffin.id === action.payload.id
    );
    muffinToLike.likes += 1;
  },

  'muffins/load_request': (state) => {
    state.muffinsLoading = true;
  },

  'muffins/load_success': (state, action) => {
    state.muffinsLoading = false;
    state.muffins = action.payload.muffins;
  },

  'muffins/load_failure': (state, action) => {
    state.muffinsLoading = false;
    state.error = action.error;
  },
});
```

This code is much more readable, isn't it? However, there are a few gotchas. It's important that when modifying the state in a reducer you either mutate the state argument or return a new state. You can't do both. Also, please read about [pitfalls of using Immer in its documentation](https://immerjs.github.io/immer/docs/pitfalls).

**IMPORTANT.** You can mutate the state only inside the `createReducer` and `createSlice` functions. I'll talk about `createSlice` later.

Please have a look at the `createReducer` docs ([https://redux-toolkit.js.org/api/createReducer](https://redux-toolkit.js.org/api/createReducer)) to learn more about it.

Now let's check what can we do with our actions. Redux Toolkit provides a helper function for generating action creators called `createAction`.

Let's generate our `likeMuffin` action using `createAction`:

```js
// src/redux/muffins.js
import { createReducer, createAction } from '@reduxjs/toolkit';

// export const likeMuffin = (muffinId) => ({
//   type: 'muffins/like',
//   payload: { id: muffinId },
// });
export const likeMuffin = createAction('muffins/like', (muffinId) => {
  return { payload: { id: muffinId } };
});
```

`createAction` takes two arguments. The first one is the action type and it's required. The second argument is a so-called _prepare_ function which you can use to accept arguments from the resulting action creator and attach these arguments as additional data to the action object. The _prepare_ function is optional.

The actions created by `createAction` have their `toString` methods overridden such that they return the action type. So, if we place our new `likeMuffin` action creator where JS expects a string, `likeMuffin` will be turned into the "muffins/like" string through the `likeMuffin.toString()` method. This means that we can use our new action creator as action type key in our reducer:

```js
// src/redux/muffins.js
const reducer = createReducer(initialState, {
  // 'muffins/like': (state, action) => {
  [likeMuffin]: (state, action) => {
    // ...
  },
  // ...
});
```

The other action we have - `loadMuffins` - is a thunk action. For generating thunk action creators Redux Toolkit provides a helper function called `createAsyncThunk`. Let's use this function to redo our `loadMuffins` thunk action:

```js
// src/redux/muffins.js
export const loadMuffins = createAsyncThunk('muffins/load', async () => {
  const response = await fetch('http://localhost:3001/muffins');
  const muffins = await response.json();
  return { muffins };
});
```

`createAsyncThunk` takes the action type as the first argument and the callback function as the second argument. The callback function should return a promise. Whatever the promise resolves with will be added to the action object's `payload` property.

`createAsyncThunk` returns a thunk action creator. When we dispatch this action creator, based on the promise we return from the callback, it dispatches the following lifecycle actions: `pending` (_muffins/load/pending_), `fulfilled` (_muffins/load/fulfilled_) and `rejected` (_muffins/load/rejected_). The types of these lifecycle actions are available as the properties of the action creator (e.g., `loadMuffins.pending`).

So, let's use these types in our reducer instead of our own `muffins/load_request`, `muffins/load_success`, `muffins/load_failure`:

```js
// src/redux/muffins.js
const reducer = createReducer(initialState, {
  // ...
  [loadMuffins.pending]: (state) => {
    state.muffinsLoading = true;
  },

  [loadMuffins.fulfilled]: (state, action) => {
    state.muffinsLoading = false;
    state.muffins = action.payload.muffins;
  },

  [loadMuffins.rejected]: (state) => {
    state.muffinsLoading = false;
    state.error = 'Failed to load muffins.';
  },
});
```

Finally, we can group the Redux functionality related to a single feature (like muffins) into a so-called "slice" (or "duck"). To accomplish this we'll use the `createSlice` function. Let's open `src/redux/muffins.js` and reorganize our Redux logic using `createSlice`:

```js
// src/redux/muffins.js
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

// ...

// Selectors...

// ...

const muffinsSlice = createSlice({
  name: 'muffins',
  initialState,
  reducers: {
    likeMuffin: {
      reducer: (state, action) => {
        const muffinToLike = state.muffins.find(
          (muffin) => muffin.id === action.payload.id
        );
        muffinToLike.likes += 1;
      },
      prepare: (muffinId) => {
        return { payload: { id: muffinId } };
      },
    },
  },
  extraReducers: {
    [loadMuffins.pending]: (state) => {
      state.muffinsLoading = true;
    },

    [loadMuffins.fulfilled]: (state, action) => {
      state.muffinsLoading = false;
      state.muffins = action.payload.muffins;
    },

    [loadMuffins.rejected]: (state) => {
      state.muffinsLoading = false;
      state.error = 'Failed to load muffins.';
    },
  },
});

export const { likeMuffin } = muffinsSlice.actions;

export default muffinsSlice.reducer;
```

This change looks a little confusing at first. So, let's discuss it part by part.

First, we no longer need `createReducer` and `createAction`, because `createSlice` creates the reducer function and basic (non thunk) actions for us.

`createSlice` expects the name of the slice, we can name it after the feature we create the slice for, like _muffins_. The name is used as a prefix for action types that are created by `createSlice` from the `reducers` option.

Then, we provide the `initialState` of the slice.

Next, `createSlice` gives two options for creating reducers: `reducers` and `extraReducers`.

We use `reducers` to create both, actions and corresponding reducers. The `reducers` option is an object which maps an action type to a corresponding reducer function. `createSlice` takes this map and generates actions and reducers from it. If an action doesn't need to keep any data apart from the action type we can create an action and reducer like this:

```js
createSlice({
  name: 'someSliceName',
  reducers: {
    helloWorld: (state) => {
      state.message = 'Hello World';
    },
  },
});
```

This creates the action creator function called `helloWorld` which returns the following action object: `{ type: 'someSliceName/helloWorld' }`. If we need to add additional data to the action object, like some payload, we can add the `prepare` function:

```js
createSlice({
  name: 'someSliceName',
  reducers: {
    helloWorld: {
      reducer: (state, action) => {
        state.message = `Hello, ${action.payload.name}`;
      },
      prepare: (name) => {
        return { payload: { name } };
      },
    },
  },
});
```

This example creates the action creator `helloWorld(name)` which takes the `name` argument and returns the following action object: `{ type: 'someSliceName/helloWorld', payload: { name } }`.

We can use `extraReducers` to create reducers for existing actions and thunk actions. Basically, you can import an action from another slice and handle it here. In our example we use `extraReducers` to handle the lifecycle actions of the `loadMuffins` thunk action.

The difference between `reducers` and `extraReducers` is that `createSlice` does not auto-generate action creators for reducers in `extraReducers`.

Both, `reducers` and `extraReducers` allow us to mutate the state argument, because both of them will be passed to `createReducer` which uses Immer to turn our state mutation to an immutable update.

`createSlice` returns an object with the following structure:

```
{
  name: name of the slice
  reducer: reducer function that combines reducers from `reducers` and `extraReducers` options
  actions: action creators extracted from the `reducers` option
  caseReducers: reducer functions from the `reducers` option
}
```

In our example we extract the action creators from the `muffinsSlice.actions` and export them separately to make it easier to import and use them in other components. And we export the reducer function by default.

So, with the help of Redux Toolkit our code became shorter and more declarative which makes it easier to read and understand it.

So, you've completed the Redux + React intro tutorial. I tried to make it as short and as simple as I could. I recommend you to check the [Redux Essentials Series](https://redux.js.org/tutorials/essentials/part-1-overview-concepts) in the Redux docs and the [Redux Toolkit website](https://redux-toolkit.js.org/). Both of them cover a lot of details, best practices and parts about Redux and Redux Toolkit that I haven't covered in this tutorial.

Thank you very much for reading my tutorial.

[View the final code on GitHub](https://github.com/d-dmytro/my-react-redux)