Web Unit 3 Sprint 10 - Redux & State Management

Module 1: The Reducer Pattern

In this module, you'll learn about the reducer pattern and how it can be used to manage state in React applications. You'll explore concepts like immutability, finite state machines, and how to effectively use reducers to handle state updates.

Learning Objectives

Content

Understanding Immutability

Understanding Immutability

Mutable objects are objects whose state is allowed to change over time. An immutable value is the exact opposite — after it has been created, it can never change. There are substantial benefits to making your state immutable:

Predictability

Mutation hides change, which can create unexpected side effects. This can lead to bugs in our code. When we enforce immutability, we can keep our application architecture and mental model simple, making it easier to reason about the application. It becomes very easy to predict how the state object will change based on certain actions/events.

Mutation Tracking

Immutability makes it easy to see if anything has changed. For example, when we change the state in Redux, our component's props will update. We can check our previous props against our new props to know what change occurred, and how to handle those changes.

Working with Immutable Data

In JavaScript, numbers and strings are naturally immutable, but objects and arrays are mutable by default. To maintain immutability when working with objects and arrays, use techniques like:

  • Object spread operator: const newObj = { ...oldObj, property: newValue }
  • Array spread operator: const newArray = [...oldArray, newItem]
  • Non-destructive array methods: map(), filter(), concat()

Finite State Machines

Finite State Machines

A state machine is a mathematical model of computation. A machine can have a finite number of states, but it can only operate in one state at a given time.

For building UIs and understanding Redux, we concentrate on state machines that have:

  • Initial state (store)
  • Current state (store)
  • Inputs or actions (action creators) that trigger transitions (reducers) to the next state

While Redux is not a finite state machine, thinking in terms of states helps our understanding of how Redux works. It's generally more helpful to think in terms of states instead of transitions.

For example, an application with a login feature could be expressed in states and actions like:

  • isLoggedOut → HTTP Request Login → pendingLoginState
  • pendingLoginState → Success/Failure → isLoggedIn
  • isLoggedIn → HTTP Request Logout → pendingLoginState
  • pendingLoginState → Success/Failure → isLoggedOut

The Reducer Hook

Using the useReducer Hook

The useReducer hook is an alternative to useState (in fact, useState actually uses useReducer hook under the hood). It's preferable when you have complex state logic in a component or when you have multiple state values that are related to each other.

Basic syntax:

const [state, dispatch] = useReducer(reducer, initialState);

The useReducer hook provides:

  • The current state value
  • A dispatch method that sends actions to the reducer when specific events occur

This hook combines the functionality of useState with the power of reducers, making it ideal for managing complex state logic within components.

Writing Reducers

Writing Effective Reducers

A reducer is a pure function that takes the current state and an action as arguments, and returns a new state. The basic pattern is:

(state, action) => newState

Key principles for writing reducers:

  • Pure functions: Reducers should be pure functions without side effects
  • Immutability: Never mutate the state directly; always return a new state object
  • Single responsibility: Each reducer should handle a specific portion of the application state
  • Predictable: The same input (state and action) should always produce the same output

A typical reducer follows this pattern:

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
}

Practice Activities

Additional Resources