introduce

Welcome to the Redux must Manual! This tutorial will introduce you to Redux and teach you how to use the latest tools and best practices we recommend in the right way. By the end of the tutorial, you should be able to start building your own Redux application using the tools and patterns learned here.

In Part 1 of this tutorial, we introduced the key concepts and terminology you need to know to use Redux, and in Part 2: Redux Application Structure, we’ll look at a basic React + Redux application to see how the pieces fit together.

Starting with Part 3: Basic Redux data Flow, we’ll use this knowledge to build a small social media feed application with some real features, see how these parts actually work in the real world, and discuss some important usage patterns and guidelines for Redux.

How to read this tutorial

This page will focus on showing you how to use Redux properly and explain enough concepts so that you can understand how to build Redux applications properly.

We try to make these instructions suitable for beginners, but we do need to make some assumptions about what you already know:

The premise condition

  • Familiar with HTML and CSS.
  • Familiar with ES6 syntax and functions.
  • Learn React terms: JSX, State, function components, Props, and Hook.
  • Understand asynchronous JavaScript and making AJAX requests.

If you are not already familiar with these topics, it is recommended that you spend some time familiarizing yourself with them before coming back to Redux. When you are ready, we will always be here!

You should make sure you have the React and Redux DevTools extensions installed in your browser:

  • React DevTools
    • React DevTools extension for Chrome
    • React DevTools extension for Firefox
  • Redux DevTools extension:
    • Redux DevTools extension for Chrome
    • Redux DevTools extension for Firefox

What is a Redux?

First, it helps to understand what “Redux” means. What does it do? What problems can you help me solve? Why should I use it?

Redux is a pattern and library for managing and updating application state with events called “actions.” It serves as a centralized store of states that need to be used throughout the application, and its rules ensure that states can only be updated in predictable ways.

Why do I need Redux?

Redux helps you manage “global” state — the state required for many parts of your application.

Redux provides patterns and tools that make it easier to understand when, where, why, and how to update state in your application, as well as how your application logic behaves when those changes occur. Redux guides you through writing predictable and testable code, which helps assure you that your application will work as expected.

When do I need Redux?

Redux can help you with shared state management, but like any tool, it comes with trade-offs. There are more concepts to learn and more code to write. It also adds indirection to the code and requires you to follow certain restrictions. It’s a tradeoff between short-term efficiency and long-term productivity.

Redux is more useful when:

  • You have a lot of application states in many places in your application
  • Application state updates frequently over time
  • The logic for updating this state can be complex
  • The application has a medium or large code base that can be used by many people

Not all applications require Redux. Take some time to think about the type of application you’re building and determine which tools will best help you solve the problem you’re solving.

Want to know more?

If you’re not sure if Redux is right for your application, these resources provide more guidance:

  • When (and when not to) use Redux
  • The Way of Redux, part 1 – Realization and intention
  • Redux FAQ: When should YOU use Redux?
  • You probably don’t need Redux

Redux libraries and tools

Redux is a small standalone JS library. However, it is commonly used with several other packages:

React-Redux

Redux can be integrated with any UI framework and is most commonly used with React. React-redux is our official software package that allows your React component to interact with the Redux Store by reading state slice and dispatching action to update the store.

Redux Toolkit

The Redux Toolkit is our recommended approach to writing Redux logic. It contains software packages and features that we believe are critical to building Redux applications. The Redux Toolkit, built on our recommended best practices, simplifies most Redux tasks, prevents common errors, and makes writing Redux applications easier.

Redux DevTools Extension

The Redux DevTools Extension shows a history of state changes over time in the Redux Store. This allows you to debug your application effectively, including using powerful techniques such as “time travel debugging.”

Redux terms and concepts

Before delving into some of the actual code, let’s talk about some of the terms and concepts you need to know to use Redux.

The State administration

Let’s start with a small React counter component. It keeps track of the number under component state and increments the number when the button is clicked:

function Counter() {
  // State: a counter value
  const [counter, setCounter] = useState(0)

  // Action: code that causes an update to the state when something happens
  const increment = () = > {
    setCounter(prevCounter= > prevCounter + 1)}// View: the UI definition
  return (
    <div>
      Value: {counter} <button onClick={increment}>Increment</button>
    </div>)}Copy the code

This is a self-contained application that contains the following sections:

  • State, the source of truth that drives our application;
  • View, UI declarative description based on current state;
  • Action, events that occur in the application based on user input, and triggering state updates;

Here is a small example of “one-way data flow” :

  • State describes the state of the application at a specific point in time
  • The UI is rendered according to this state
  • When something happens, such as a user clicking a button, state is updated based on what happens
  • The UI is rerendered according to the new state

However, simplicity can break down when we have multiple components that need to share and use the same state, especially when those components are in different parts of the application. Sometimes this can be done by “promoting state” to the parent component, but this is not always helpful.

One way to solve this problem is to extract shared state from the component and place it in a centralized location outside the component tree. This way, our component tree becomes one big “view,” and any component can access state or trigger action, no matter where they are in the tree!

By defining and separating the concepts involved in state management and enforcing principles that maintain independence between View and State, we provide more structure and maintainability to our code.

This is the basic idea behind Redux: a single, centralized location to contain global state in your application, and a specific pattern to track state updates, making code logic and results predictable.

immutability

“Variable” means “changeable”. If something is “immutable”, it can never be changed.

By default, JavaScript objects and arrays are mutable. If you create an object, you can change the contents of its fields. If you create an array, you can also change the contents:

const obj = { a: 1.b: 2 }
// still the same object outside, but the contents have changed
obj.b = 3

const arr = ['a'.'b']
// In the same way, we can change the contents of this array
arr.push('c')
arr[1] = 'd'
Copy the code

This is called making an object or array change. It leaves the object or array reference in memory unchanged, but now the contents inside the object have changed.

To update values consistently, your code must make a copy of the existing object/array and then modify the copy.

We can do this manually by using JavaScript’s array/object destruct operator and returning a new copy of the array, rather than changing the original array method:

const obj = {
  a: {
    // To safely update obj.a.c, we have to copy each piece
    c: 3
  },
  b: 2
}

const obj2 = {
  // copy obj. obj,// overwrite a
  a: {
    // copy obj.a. obj.a,// overwrite c
    c: 42}}const arr = ['a'.'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
Copy the code

Redux expects all state updates to be static. Later, we’ll look at where and how this becomes important, as well as some simpler ways to write immutable object/array update logic.

Want to know more?

For more information about how immutability works in JavaScript, see:

  • A visual guide to JavaScript references
  • Immutability in React and Redux: a complete guide

The term

Before proceeding, you need to familiarize yourself with some important Redux terms:

Action

An Action is a plain JavaScript object with a Type field. You can think of actions as events that describe what happens in your application.

The Type field should be a string that gives the action a descriptive name, such as “todos/todoAdded”. We typically write this as a string like “Domain /eventName”, where the first part is the function or category to which the action belongs and the second part is the specific event that occurred.

The Action object can have additional fields that contain additional information about the event that occurred. By convention, we put this information in a field called payload.

A typical action object might look like this:

const addTodoAction = {
  type: 'todos/todoAdded'.payload: 'Buy milk'
}
Copy the code

Action Creator

Action Creator is a function that creates and returns an Action object. Typically, we create these objects in code, so we don’t have to write the action object manually each time:

const addTodo = text= > {
  return {
    type: 'todos/todoAdded'.payload: text
  }
}
Copy the code

Reducer

Reducer is a function that receives the current state and an action object, decides how to update the state if necessary, and returns a newState :(state, action) => newState. You can treat the Reducer as an event listener that processes events based on the action (event) type received.

information

The “Reducer” functions are used because they are similar to the callback functions you pass to the array.reduce () method.

Reducer must always follow certain rules:

  • They should only be based onstateandactionParameter to calculate the new state value
  • They are not allowed to modify existing states. Instead, they must be immutable by copying the existing state and making changes to the copied values.
  • They must not perform any asynchronous logic, calculate random values, or cause other “side effects”

Later, we will discuss the reducer’s rules in detail, including their importance and how to follow them properly.

The logic inside the Reducer function generally follows the following series of steps:

  • Check whether the Reducer cares about this action
    • If so, copy state, update the copy with the new value, and return it
  • Otherwise, keep the original state unchanged

Here is a small example of a Reducer, showing the steps that each Reducer should follow:

const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // Check to see if the reducer cares about this action
  if (action.type === 'counter/increment') {
    // If so, make a copy of `state`
    return {
      ...state,
      // and update the copy with the new value
      value: state.value + 1}}// otherwise return the existing state unchanged
  return state
}
Copy the code

Reducer can use any internal logic to determine the new state, including: if/else, switch, loop, etc.

Clarification: Why are they called “Reducer”?

The array.reduce () method lets you pass in an Array, loop through each item in the Array, one at a time, and return a single final result. You can think of it as “reducing an array to one value.”

Array.reduce() takes the callback function as an argument, and each item in the Array is called once. The callback takes two arguments:

  • previousResult, the last value returned by your callback function;
  • currentItem, the current item in the array;

The first time we run the callback, there is no previousResult available, so we also need to pass an initial value that will be used as the first previousResult.

If we wanted to add up an array of numbers to find the total, we could write a reduce callback like this:

const numbers = [2.5.8]

const addNumbers = (previousResult, currentItem) = > {
  console.log({ previousResult, currentItem })
  return previousResult + currentItem
}

const initialValue = 0

const total = numbers.reduce(addNumbers, initialValue)
// {previousResult: 0, currentItem: 2}
// {previousResult: 2, currentItem: 5}
// {previousResult: 7, currentItem: 8}

console.log(total)
/ / 15
Copy the code

Note that the addNumbers “Reduce callback” function does not need to track anything. It takes the previousResult and currentItem arguments, processes them, and returns the new result value.

The Redux Reducer function is exactly the same as this “Reduce callback” function! It takes the previous result (that is, state) and the current item (that is, the Action object), determines a new state value based on these parameters, and returns the new state.

If we were to create an array of Redux actions, call reduce() and pass a reducer function, we would get the final result in the same way:

const actions = [
  { type: 'counter/increment' },
  { type: 'counter/increment' },
  { type: 'counter/increment'}]const initialState = { value: 0 }

const finalResult = actions.reduce(counterReducer, initialState)
console.log(finalResult)
// {value: 3}
Copy the code

You can say that the Redux Reducer reduces a set of actions (over time) to a single state. The difference is that with array.reduce () it happens all at once, whereas with Redux it can happen throughout the life of a running application.

Store

The current Redux application state exists in an object called store.

This store is created by passing in a reducer and has a method called getState that returns the current state value:

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({ reducer: counterReducer })

console.log(store.getState())
// {value: 0}
Copy the code

Dispatch

The Redux Store has a method called dispatch. The only way to update state is to call store.dispatch() and pass in the Action object. The store will run its reducer function and save the new state value there, and we can call getState() to retrieve the updated value:

store.dispatch({ type: 'counter/increment' })

console.log(store.getState())
// {value: 1}
Copy the code

You can think of the Dispatch action as a “trigger event” in your application. Something happened, and we want Store to know that. The Reducer behaves like an event listener, and when they hear an action they are interested in, they update the state in response to that state.

We usually require action Creator Dispatch to have the correct action:

const increment = () = > {
  return {
    type: 'counter/increment'
  }
}

store.dispatch(increment())

console.log(store.getState())
// {value: 2}
Copy the code

Selector

A Selector is a function that knows how to extract specific information from a store’s state value. As the application becomes larger, this avoids repetitive logic, since different parts of the application need to read the same data:

const selectCounterValue = state= > state.value

const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
/ / 2
Copy the code

Redux application data flow

Earlier, we discussed “one-way data flow,” which describes the following sequence of steps to update the application:

  • State describes the state of the application at a specific point in time
  • The UI is rendered according to this state
  • When something happens, such as a user clicking a button, state is updated based on what happens
  • The UI is rerendered according to the new state

For Redux in particular, we can break these steps down into more detail:

  • Initial Settings:
    • Create a Redux store using the root Reducer function
    • Store calls the root reducer once and saves the return value as its originalstate
    • When the UI is first rendered, the UI component accesses the current state of the Redux Store and uses that data to decide what to render. They also subscribe to any future store updates, so they can see if the state has changed.
  • Update:
    • Something happens in the application, such as a user clicking a button
    • The application code dispatches an action to the Redux store, for exampledispatch({type: 'counter/increment'})
    • Store runs the Reducer function again with the previous state and the current action and saves the return value as the new state
    • The Store will notify all uIs subscribed to — some stores have been updated
    • Each UI component that needs to fetch data from the Store checks to see if its required state portion has changed.
    • Each component that sees its data changed forces a re-rendering with the new data, so it can update what is displayed on the screen

The visualization of the data flow is as follows: