An Intro to Redux
Tags: redux, react, javascript, tutorial
Published: 2021-03-10 02:30:08

*This article was originally published at [haluza.dev](https://haluza.dev/blog/an-introduction-to-redux)*

**What you'll get out of this article:**

- Learn why developers use external libraries to manage state in React
- Understand the fundamentals of Redux
- Apply Redux concepts to a simple counter app
- Learn how Redux Toolkit simplifies Redux setup

**This article is for you if:**

- You're familiar with the basics of React
- You know how to manage React state with hooks and/or state objects
- You're new to state management libaries like Redux and MobX

---

If you're wondering why this article discusses vanilla Redux and not Redux Toolkit, please read [my explanation in the afterword](#why-this-article-uses-vanilla-redux).

## Table of Contents

1. [Introduction](#introduction)
2. [Why Do We Need Redux?](#why-do-we-need-redux)
3. [How Does Redux Work?](#how-does-redux-work)
4. [Understanding Redux in an App](#understanding-redux-in-an-app)
   - [Actions & Action Creators](#Actions-&-Action-Creators)
   - [Reducers](#reducers)
   - [Store](#store)
   - [Connecting the App to the Store](#connecting-the-app-to-the-store)
5. [Summary](#summary)
6. [Next Steps](#next-steps)
   - [Read "You Might Not Need Redux"](#read-you-might-not-need-redux)
   - [Build an App With Redux](#build-an-app-with-redux)
   - [Explore Redux Toolkit](#explore-redux-toolkit)
7. [Afterword: Why This Article Uses Vanilla Redux](#why-this-article-uses-vanilla-redux)

## Introduction <span id="introduction"></span>

State management is one of the core concepts of React. It's also one of the most complicated. This isn't necessarily because managing state in React is tricky; rather, there are so many different ways to do it!

In this article I'm going to assume that you're comfortable managing state within a component, but are relatively new to Redux.

At the simplest level, Redux lets you do two things:

- Manage state from a single location in your app
- Access this state anywhere in your app, without passing it from component to component

To understand why this is so important, let's take a moment to imagine we've been hired to create a new hit app.

## Why Do We Need Redux? <span id="why-do-we-need-redux"></span>

Our product manager wants us to build an app called Counter. It's fast, sleek, and consists of a single component. (Think of how small the bundle size is!)
Check out the code below, or click [here](https://codesandbox.io/s/usestate-counter-example-djq6s?file=/src/App.js) to view this as an app on CodeSandbox.

```javascript
export default function App() {
  const [count, setCount] = useState(0);
  const increment = () => {
    setCount((prevCount) => prevCount + 1);
  };
  const decrement = () => {
    setCount((prevCount) => prevCount - 1);
  };
  const reset = () => {
    setCount(0);
  };
  return (
    <div className="App">
      <h1>Counter - No Redux</h1>
      <div className="counter">
        <button onClick={decrement}>-</button>
        {count}
        <button onClick={increment}>+</button>
      </div>
      <button onClick={reset}>Reset</button>
    </div>
  );
}
```

Inside this tiny `App` component, we're creating a single `count` state for our counter, initializing it to `0`, and defining methods to `increment`, `decrement`, and `reset` it.

Then we're implementing the counter inside the same component.

If your React apps are all as simple as this one, you'll never need to use a state management solution like Redux. However, I can all but guarantee that you'll work on an app in which `useState` or `setState` alone won't cut it.

### Example 2: Complex Counter

Turns out our counter app was massively popular — it's time to introduce the
world to Counter 2.0!

Here's the mockup our product manager just gave us. Note that it's a _little_ more complicated than what we were working with before:

[![Mockup-for-a-complicated-app](https://res.cloudinary.com/dbvsstxde/image/upload/v1614696165/haluza.dev/Redux%20Post/complicated-app_g62nrb.png)]()

To save you some stress, we aren't going to code this app out. Instead, I want you to think of the different types of state that we would need to manage inside this app. Off the top of my head, here are the key types of state we would need to manage:

- All of the counters in the app, as well as their current values. We could store the counter values inside an array to keep track of the counters more easily.
- Login-related info, such as the user's name, so we could display it in the
  UI.
- The current color theme (light mode or dark mode)

Previously, we stored all of our state logic inside our `App.js` file. Now, however, our state is a little bigger. Below you'll see our current state represented as an object. Why did I use an object? Keep that question in mind as you read on.

```javascript
const initialState = {
  username: '',
  counters: [0, 17],
  colorTheme: 'light',
};
```

Well, that doesn't seem so bad. But hold on — don't we also need to include methods to trigger state changes?

```javascript
const setUsername = (username) => {
  // logic to set the username when someone logs in
}
const addCounter = () = => {
  // logic to add a counter
}
const removeCounter = (index) => {
  // logic to remove a counter at a certain index
}
const increment = (index) => {
  // logic to increment a specific counter
}
const decrement = (index) => {
  // logic to decrement a specific counter
}
const reset = (index) => {
  // logic to reset a specific counter
}
```

We've just defined the basic business logic for our application. We already have some problems.

1. Our `App.js` component is going to get crowded if we move it all there.
2. It's going to get even more crowded if we start adding more state and logic
   to our app.
3. We'll also need to pass our state and methods down to our components. And if
   we nest components inside other components (for example, `App` ->
   `CounterContainer` -> `Counter`), we run the risk of introducing
   [prop drilling](https://kentcdodds.com/blog/prop-drilling) into our app.

Wouldn't it be easier if we had one central place to store our state and our state-related methods, like adding counters and changing the color theme? And wouldn't it also be great if we could grab state and methods directly from this central store, instead of passing them through component after component?

This is where Redux comes in.

## How Does Redux Work? <span id="how-does-redux-work"></span>

Counter 2.0 shows us some very common state management issues that can occur in
React apps when they grow more complex. Redux helps solve these problems by
handling state management in a very opinionated and clearly defined flow.

Here's how Redux's "one-way data flow" works. Just soak it in — it's OK if it doesn't make sense yet.

[![redux-flow-diagram](https://res.cloudinary.com/dbvsstxde/image/upload/v1614696165/haluza.dev/Redux%20Post/redux-diagram_un9kun.png)]()

Let's translate this image into a series of written steps. For now, let's imagine that we've implemented Redux inside a simple counter app, like Counter 1.0.

This is what happens when a user clicks on the button to increment the counter from `0` to `1`.

- The app **dispatches** an **action**. The action is a function called `increment`.
- The action is sent to the **store**, which holds the app's state inside an object.
- The store updates the state using a **reducer function** (more on that
  later).
  - In this case, the `count` state is increased to `1`.
- The store sends the updated state back to the UI. The counter now displays `1` instead of `0`.

Actions, stores, reducers... This is getting extremely abstract. To make these concepts more tangible, let's see an how Redux works inside a React app.

## Understanding Redux in an App <span id="understanding-redux-in-an-app"></span>

Remember Counter 2.0? Our product manager decided to scrap it because it was too complicated. Now they want us to build the much simpler and much prettier Counter 3.0. Oh, and they want us to use Redux!

Here's what the finished app looks like. Before moving on, poke around inside the app and get a feel for its functionality. Inside the `redux` directory, you'll find some files with familiar sounding names, like `reducer.js`, `actionCreators.js`, and `store.js`.

{% codesandbox 4rpht %}

We're going to explore the following concepts inside the Counter 3.0 app:

- Reducers
- Actions (and action creators)
- Store

Let's take a look at that Redux flow diagram again. It's important to keep these concepts in mind as you explore the app.

[![redux-flow-diagram](https://res.cloudinary.com/dbvsstxde/image/upload/v1614696165/haluza.dev/Redux%20Post/redux-diagram_un9kun.png)]()

### Actions & Action Creators <span id="actions-&-action-creators"></span>

Before I explain what an action or an action creator is, let's look at a simplified version of the `actionCreators.js` file.

```javascript
export const incrementCounter = () => {
  return {
    type: 'INCREMENT_COUNTER',
  };
};

export const decrementCounter = () => {
  return {
    type: 'DECREMENT_COUNTER',
  };
};

export const resetCounter = () => {
  return {
    type: 'RESET_COUNTER',
  };
};

export const setCustomCount = (customCount) => {
  return {
    type: 'SET_CUSTOM_COUNT',
    payload: customCount,
  };
};
```

Here we've created functions to define four events we can trigger with our app:

- Increment the count
- Decrement the count
- Reset the count
- Set the count to a custom number

Each of these events corresponds to a button in the app.

These functions are called **action creators**. Each action creators returns an object called an **action**.

There are two basic types of actions.

The first contains only a `type` property. Think of it as the action's
**label**.

```javascript
{
  type: 'INCREMENT_COUNTER';
}
```

The second contains a `type` property as well as a `payload` property.

```javascript
{
  type: "SET_CUSTOM_COUNT",
  payload: 67
}
```

The name `payload` is an apt description. It's the value(s) we want to use when we update the state. In the case of our `SET_CUSTOM_COUNT` action, we're updating the `count` state to `67`.

Why don't any of our other actions contain payloads? Simple: they don't need them. We'll see why when we learn about reducers next.

Where do we trigger our reducers? Right inside the app. Here's the code for our "increment" button:

```javascript
<button onClick={() => dispatch(incrementCounter())}>+</button>
```

We'll discuss the `dispatch` method later. But in a nutshell, here's what happens when a user clicks the `+` button to increment the counter.

1. The `incrementCounter` function (action creator) is executed.
2. `incrementCounter` returns an object with a `type` property of
   `INCREMENT_COUNTER`. This object is our **action.**
3. The action is sent to the **reducer.**

### Reducer <span id="reducer"></span>

This is where it starts to come together.

What's the reducer? It's simply a function that controls your app's state.

It's often written as a [switch statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch), as is the one in this app, but that's simply a common convention, not a requirement.

Here's what our reducer looks like:

```javascript
const initialState = {
  count: 0,
};

export default function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT_COUNTER':
      return {
        count: state.count + 1,
      };
    case 'DECREMENT_COUNTER':
      return {
        count: state.count - 1,
      };
    case 'RESET_COUNTER':
      return {
        count: 0,
      };
    case 'SET_CUSTOM_COUNT':
      return {
        count: action.payload,
      };
    default:
      return state;
  }
}
```

That's a lot to take in. Let's walk through this chunk of code step by step.

- First, we define our `initialState` as an object above the reducer.
- Next, the reducer function accepts two parameters: `state` and `action`.
  - `state` - the `initialState` object is this parameter's default value.
  - `action` - this refers to whatever action that was just returned by the action creator.
- We create a switch statement. Inside this statement, we return an **object** depending on the action's type property.

If a user opens the app and chooses to increment the counter, what happens?

- The app dispatches the `incrementCounter` action creator:

```javascript
const incrementCounter = () => {
  return {
    type: 'INCREMENT_COUNTER',
  };
};
```

- The `incrementCounter` action creator returns an object (an **action**) with a `type` property of `INCREMENT_COUNTER`.

```javascript
{
  type: 'INCREMENT_COUNTER';
}
```

- Our **reducer** function is invoked, accepting `initialState` and the action object as parameters. In pseudocode, it looks something like this:

```javascript
const initialState = {
  count: 0,
};

const incrementAction = { type: 'INCREMENT_COUNTER' };

counterReducer(initialState, incrementAction);
```

- The **reducer** looks at the action's `type` property and sees if it matches any of its cases. Bingo - we hit the `INCREMENT_COUNTER` case.

```javascript
switch (action.type) {
  case 'INCREMENT_COUNTER':
    return {
      count: state.count + 1,
    };

  // other cases here...

  default:
    return state;
}
```

- The reducer returns an object with a single property, `count`. To calculate the value, it grabs the current value of `count` from the current state object (which is `0` now) and adds `1` to it.

```javascript
{
  count: 1;
}
```

Hold on — that looks a lot like our `initialState` object!

```javascript
// Our initial state object
const initialState = {
  count: 0,
};

// The object returned by the reducer
{
  count: 1;
}
```

That's right. The reducer returns the updated state. In more technical terms, it replaces the previous state object with a new state object containing updated values. This is because Redux state is **immutable** (key interview term!). You should never directly modify your Redux state inside your reducer. Instead, you should return a brand new object, like we do here.

This updated state object is now available for our app to use. But how does our app have access to the state?

It's time to learn about the store.

## Store <span id="store"></span>

Here's what Counter 3.0's store looks like. Brace yourself... it's 4 lines of code.

```javascript
import { createStore } from 'redux';
import counterReducer from './reducer';

const store = createStore(counterReducer);

export default store;
```

Still, we only need to look at one line:

```javascript
const store = createStore(counterReducer);
```

A Redux store is simply an object that holds your app's state. **Your app
should only contain one store.** This is a **HUGE** part of what makes Redux an appealing state solution. Your store becomes a **single source of truth** for your app's state.

Remember the phrase "single source of truth." It's an easy way to sum up the benefits of Redux. Plus, it's another great phrase to use in interviews.

In the line of code above, Redux's `createStore` function takes in your reducer and uses it to construct the store object.

As your app grows more complex, you may want to create multiple reducers. If we add a to-do feature to our counter app, creating a separate `toDoReducer` where
we store our state and methods for our app's "to-do" functionality.

Fortunately, the Redux library provides a [`combineReducers`](https://redux.js.org/api/combinereducers) function that lets you feed a multilayered reducer to your store.

We're almost there! We've built our action creators, reducer, and store. Now we just need to give our app access to the store and the state inside it.

### Connecting the App to the Store <span id="connecting-the-app-to-the-store"></span>

There are only two steps left:

1. Wrap our store around our entire app, using a special wrapper component called `Provider`.
2. Hook our components into the store with... Redux hooks!

Hang in there. This is the home stretch!

**Wrapping the Store Around Our App**

For these last few steps, we're going to use a few features that the [React Redux](https://react-redux.js.org/) library gives us. The first one is called `Provider`, and it's a component that we wrap around our entire app. We use it in the `index.js` file.

Here's the `index.js` file of a typical React app.

```javascript
import ReactDOM from 'react-dom';

import App from './App';

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
```

Here's what the same file looks like when we implement the `Provider` component.

```javascript
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './redux/store';

import App from './App';

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

This file suddenly got a lot more busy. The key difference is this chunk of code:

```javascript
<Provider store={store}>
  <App />
</Provider>
```

We're _providing_ the entire app with access to our Redux store. And this is a big thing. It means that regardless of where we are in our app — even if we're inside a component nested a dozen layers down — we can reach directly into the store without even leaving that component.

We no longer need to pass down all our state as props.

**Accessing State From Inside a Component**

Finally, let's look at two hooks: `useSelector` and `useDispatch`.

- `useSelector` lets us access state values inside our store (like our `count`
  state).
- `useDispatch` lets us "dispatch" action creators to our reducer. In other
  words, it lets us trigger state changes, like incrementing a counter.

Think of `useSelector` as a **noun** (e.g. `count`) and `useDispatch` as a **verb** (e.g. `incrementCounter`).

Inside our app's `Counter.js` file, we implement both of these hooks.

```javascript
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
  incrementCounter,
  decrementCounter,
  resetCounter,
} from '../redux/actionCreators';

const Counter = () => {
  const count = useSelector((state) => state.count);
  const dispatch = useDispatch();

  return (
    <div className="counter">
      <div className="counter-top">
        <button onClick={() => dispatch(decrementCounter())}>-</button>
        <p>{count}</p>
        <button onClick={() => dispatch(incrementCounter())}>+</button>
      </div>
      <button onClick={() => dispatch(resetCounter())}>Reset</button>
    </div>
  );
};

export default Counter;
```

At the top of the `Counter` component, we do two important things:

1. Use the `useSelector` hook to access the value of the `count` property inside our store's `state` object, and then save it inside a constant named `count`.
2. Invoke the `useDispatch` hook. The result, which we save as the constant `dispatch`, is a reference to the `dispatch` function in the Redux store.

That's all we need to work with our store!

For the `useDispatch` hook, we do need to import any actions we're going to use, so we can invoke it as such:

```javascript
<button onClick={() => dispatch(incrementCounter())}>+</button>
```

We can also pass a payload to the action creator if needed:

```javascript
<button onClick={() => dispatch(setCustomCount(419))}>
  Set Counter to 419
</button>
```

And...that's it! We've hooked our app up to our Redux store.
[Here's the link](https://codesandbox.io/s/redux-counter-4rpht?file=/src/App.js) to the finished app, in case you don't want to scroll all the way back up to the sandbox.

And here's the code!

{% codesandbox 4rpht %}

_For a more detailed look at `useSelector` and `useDispatch`, please refer to the React Redux documentation:_

- [`useSelector`](https://react-redux.js.org/next/api/hooks#useselector)
- [`useDispatch`](https://react-redux.js.org/next/api/hooks#usedispatch)

## Summary <span id="summary"></span>

We covered a massive amount of ground in this article.

Here are the key concepts we covered:

- Redux is a state management library that acts as the **single source of truth** for your app's state-related logic.
- To implement Redux, you should implement the following in your app:
  - **Action creators:** functions that are dispatched when your app triggers an action.
    - Every action creator returns an **action**, an object with instructions for updating the state.
  - **Reducers:** functions that take a state object and action as parameters, and return an object containing the app's updated state.
  - **Store:** An object containing the entirety of your app's Redux state.
- To give your app access to the store, wrap it inside a `Provider` component.
- Use the `useSelector` and `useDispatch` hook to access state and dispatch action creators from inside any component inside your app.

If you're feeling lost, that's normal. It took me at least three separate tries to understand Redux well enough to implement it in a tiny app.

If you're having trouble with these concepts, take some time to check out the excellent explanations provided in the official [Redux documentation] (https://redux.js.org/tutorials/essentials/part-1-overview-concepts).

## Next Steps <span id="next-steps"></span>

As you're getting more comfortable with Redux, I highly recommend that you do the following:

### Read "You Might Not Need Redux" <span id="read-you-might-not-need-redux"></span>

[Dan Abramov](https://twitter.com/dan_abramov) is famous for creating Redux and working on Create React App and React hooks. He also wrote a very insightful article called
[_You Might Not Need Redux_](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367).

Redux is a great tool to have, but it's just that — a tool. You shouldn't use it if you don't need it. For smaller apps, React state may be enough. For larger apps, you may find yourself using a mixture of Redux state for data used globally and React state for more localized state.

### Build an app with Redux <span id="build-an-app-with-redux"></span>

I want you to implement Redux in a React app. I recommend keeping the app as simple as possible; this will let you focus more on the implementation of Redux, as opposed to React itself.

Some ideas:

- Build a score counter for a sports game (any sport of your choice). Give users the option to add points for either team. You can even include a winning condition (one team wins when they attain a certain number of points).
- Build your own counter, using Counter 3.0 (the one we just finished going
  over) as a reference.
- Up for a challenge? Create a simplified ecommerce app with a shopping cart that displays items as you click on them.

Feel free to use this sandbox as a reference. It's our counter from before, to include some best practices that are explained in the comments.

{% codesandbox bl482 %}

### Explore Redux Toolkit <span id="explore-redux-toolkit"></span>

I mentioned Redux Toolkit at the very beginning of this post. Once you're comfortable with how Redux works, you should make an effort to move to Redux Toolkit. It simplifies a lot of the code that we just wrote. After working with vanilla Redux, you'll see the benefits immediately.

Redux Toolkit was built by the Redux.js team and is described as "the official, opinionated, batteries-included toolset for efficient Redux development" on [the library's site](https://redux-toolkit.js.org/).

As someone who cut their teeth on Redux and then moved to Redux Toolkit, trust me when I say it's the way that any team should work with Redux logic.

But wait - if Redux Toolkit is the modern Redux implementation you should use, why did we spend an entire article using vanilla Redux?

---

## Afterword: Why This Article Uses Vanilla Redux (Instead of Redux Toolkit) <span id="why-this-article-uses-vanilla-redux"></span>

I believe that the basic Redux.js library provides the most direct way to learn how Redux works. With Redux Toolkit, you're able to leverage many new APIs that improve on Redux's functionality. However, to really grasp what these improvements are doing, and why they're so important, you need a firm understanding of how Redux works.

For instance, Redux Toolkit's [`createSlice`](https://redux-toolkit.js.org/api/createSlice) API is one of my favorite features, as it removes the need to create a separate file for your action creators - it automatically generates them from your reducer. To really understand how powerful this is, you should have a solid understanding of what action creators and actions are.

In other words:

- Vanilla Redux lets you learn Redux with the smallest amount of abstractions
- Redux Toolkit builds on the original Redux library with more powerful APIs, and you should use it once you understand how Redux works

It's also worth mentioning that some teams with older codebases may still be using the older version of Redux, just as many React codebases will feature
class-based state instead of hooks (or a mixture of the two). While this shouldn't be your motivation for learning vanilla Redux, it's definitely a side benefit that makes you more versatile.

---

We've covered so much knowledge in this post. Take a break and let it sink in before you do anything else!