Reprinted from Kingsley Silas. Understanding How Reducers are Used in Redux. Oct 24, 2019

Reducer is a function that determines changes in application state. It uses the actions it receives to determine this change. We have tools, such as Redux, that help manage the state changes of applications in a single store and make them behave consistently.

Why do we mention Redux when we talk about reducers? Redux relies heavily on reducer functions that take advantage of the previous state and an action to perform the next state.

We will focus directly on reducer in this article. Our goal is to become familiar with using the Reducer function so that we can see how it is used to update the state of the application — and ultimately understand the role they play in the state manager, such as Redux.

What do you mean, “state”

A state change is a user-based interaction, even something like a network request. If the state of the application is managed by Redux, the change occurs in the Reducer function — this is the only place where the state change occurs. The Reducer function uses the initial state of the application and something called action actions to determine what the new state will look like.

If we are in math class, we can say.

initial state + action = new state
Copy the code

From the actual Reducer function, it looks like this.

const contactReducer = (state = initialState, action) = > {
  // Do something
}
Copy the code

Where do we get our initial state and behavior? These are the things we’re going to define

Parameters of the state

The state parameter passed to the Reducer function must be the current state of the application. In this case, we call it initialState because it will be the first (and current) state, and there will be nothing before it.

contactReducer(initialState, action)
Copy the code

For example, the initial state of our application is an empty contact list, and our action is to add a new contact to the list.

const initialState = {
  contacts: []}Copy the code

This creates our InitialState, which is equal to the parameter state we needed in the Reducer number.

Parameters of the action

An action is an object that contains two keys and their values. Status updates that occur in the Reducer always depend on the value of action.type. In this scenario, we demonstrate what happens when a user tries to create a new contact. So, let’s define action.type as NEW_CONTACT.

const action = {
  type: 'NEW_CONTACT'.name: 'John Doe'.location: 'Lagos Nigeria'.email: '[email protected]'
}
Copy the code

There is usually a payload that contains the content sent by the user and is used to update the status of the application. Note that action.type is required, but action.payload is optional. The use of payload brings a degree of structure to the appearance of the action object.

Update the status

State means that it is immutable, which means that it should not be changed directly. To create an updated state, we can either use the Object.assign or select the spread operator.

Object.assign

const contactReducer = (state, action) = > {
  switch (action.type) {
    case 'NEW_CONTACT':
    return Object.assign({}, state, {
      contacts: [
        ...state.contacts,
        action.payload
      ]
    })
    default:
      return state
  }
}
Copy the code

In the example above, we use object.assign () to ensure that we don’t change the state value directly. Instead, it allows us to return a new object full of states passed to it and payloads sent by the user.

To use object.assign (), it is important that the first argument is an empty Object. Passing the state as the first parameter causes it to be mutated, which is also something we want to avoid in order to maintain consistency.

Spread the operator

The alternative to object.assign() is to use the spread operator, like this.

const contactReducer = (state, action) = > {
  switch (action.type) {
    case 'NEW_CONTACT':
    return {
        ...state, contacts:
        [...state.contacts, action.payload]
    }
    default:
      return state
  }
}
Copy the code

This ensures that the inbound state remains the same when we append the new item to the bottom.

Use the switch statement

Earlier, we noticed that the update that occurs depends on the value of action.type. The switch statement conditionally determines the type of update we want to process based on the value of action.type.

This means that a typical Reducer would look like this.

const addContact = (state, action) = > {
  switch (action.type) {
    case 'NEW_CONTACT':
    return {
        ...state, contacts:
        [...state.contacts, action.payload]
    }
    case 'UPDATE_CONTACT':
      return {
        // Handle contact update
      }
    case 'DELETE_CONTACT':
      return {
        // Handle contact delete
      }
    case 'EMPTY_CONTACT_LIST':
      return {
        // Handle contact list
      }
    default:
      return state
  }
}
Copy the code

It is important that we return our “default “state when the value of “action.type” specified in the Action object does not match the value in the Reducer — for example, if for some unknown reason the action looks like this.

const action = {
  type: 'UPDATE_USER_AGE'.payload: {
    age: 19}}Copy the code

Since we don’t have this type of action, we want to return the contents of the state (the current state of the application) instead. In short, we’re not sure what the user is trying to accomplish at this point.

Put everything together

Here is a simple example of how I implemented the Reducer function in React.

const initialState = {
  contacts: [{
    name: 'Vic Henry'.age: 30}};const contactReducer = (state = initialState, action) = > {
  switch (action.type) {
    case "NEW_CONTACT":
      return Object.assign({}, state, {
        contacts: [...state.contacts, action.payload]
      });
    default:
      returnstate; }};class App extends React.Component {
  constructor(props) {
    super(props);
    this.name = React.createRef();
    this.age = React.createRef();

    this.state = initialState;
  }

  handleSubmit = e= > {
    e.preventDefault();
    const action = {
      type: "NEW_CONTACT".payload: {
        name: this.name.current.value,
        age: this.age.current.value
      }
    };
    const newState = contactReducer(this.state, action);
    this.setState(newState);
  };

  render() {
    const { contacts } = this.state;
    return (
      <div className="box">
        <div className="content">
          <pre>{JSON.stringify(this.state, null, 2)}</pre> 
        </div>
        
        <div className="field">
          <form onSubmit={this.handleSubmit}>
            <div className="control">
              <input className="input" placeholder="Full Name" type="text" ref={this.name} />
            </div>
            <div className="control">
              <input className="input" placeholder="Age" type="number" ref={this.age} />
            </div>
            <div>
              <button type="submit" className="button">Submit</button>
            </div>
          </form>
        </div>
      </div>
    );
  }
}


ReactDOM.render(
  <App />.document.getElementById('root'));Copy the code

As you can see, I’m not using Redux, but this is very much the same way that Redux uses Reducer to store and update state changes. The main status updates occur in the Reducer function, which returns values that set the update status of the application.

Want to give it a try? You can extend the Reducer function to let the user update the age of the contact. I’d like to see what you think in the comments section!

Understanding the role that reducer plays in Redux should give you a better understanding of what is happening below the engine. If you are interested in reading more about using Reducer in Redux, check out the official documentation.