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.
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:
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.
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.
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:
const newObj = { ...oldObj, property: newValue }
const newArray = [...oldArray, newItem]
map()
, filter()
, concat()
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:
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:
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:
state
valuedispatch
method that sends actions to the reducer when specific events occurThis hook combines the functionality of useState
with the power of reducers, making it ideal for managing complex state logic within components.
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:
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; } }