Intro to Application State in React

Part 1 of how to manage application level state in React apps.

Intro to Application State in React

I’m going to assume you already have a basic understanding of component level state in React. You also don’t have to spend that much time with React before you want to manage state at the application level.

If you know what I mean by “prop drilling” and/or passing props and callback functions through multiple components because a deeply nested child component needs a particular value, then your app is ready for application state management.

This is part 1 of a series on application state management. This is the introduction to terms and concepts. Part 2 (coming soon) will cover state management with React Hooks, and part 3 (coming soon) will cover Redux.

There’s 3 parts to application level state management. There’s the store, which holds the state. There are actions, which are javascript objects with a type and optional payload:

{
  type: ACTION_TYPE,
  payload: { ...someOptionalData }
}

And there are are the reducers. You can think of reducers as a fancy way of saying, “This function updates the state.”

There are 2 things to keep in mind here. One is that reducers are pure functions with zero side-effects. This means that, given certain inputs, it should always return the same output. It also can’t alter any variables outside of of its scope, such as global variables. The other thing is, state is immutable. The reducer doesn’t update the state itself, but rather it creates a new state object with the updated values, and returns that.

const reducer = (state, action) => {
  return {
    ...state,
    action.payload
  };
};

Hang on a second, this reducer always does the same thing. What about action.type?

Good question, my friend.

Reducers usually perform different actions, based on action.type. This is usually seen in a switch statement.

Let’s look at a simple counter example here. The state holds the current count, and the 2 actions it can take are to increment or decrement the count by an amount stored in payload.

// Example action:
// {
//   type: 'INCREMENT',
//   payload: { amount: 5 }
// }

const reducer = (state, action) => {
  switch (action.type) {
    case "INCREMENT":
      return {
        ...state,
        count: state.count + action.payload.amount,
      }

    case "DECREMENT":
      return {
        ...state,
        count: state.count - action.payload.amount,
      }

    default:
      return state
  }
}

The ...state is the spread operator. It takes the current state and populates the new object with its values. The next line then updates the specific property.

And that’s really all reducers are. I know it’ll make more sense once you see it in the context of an application. But it’s important to understand the 3 parts (store, actions, and reducers) of application state management before we go into specific implementations.

Recap

  • A reducer takes an action, and updates the state based on the action.
  • The store holds the state itself.
  • The action is an object with a type (instruction), and an optional payload (data to be updated).
  • State is immutable. The reducer creates a new state object and passes that to the store.
  • A reducer is a pure function with no side-effects. It always produces the same output given the same inputs.

In part 2 (coming soon), we’ll explore managing application state with React Hooks.