Once you start working with React in anger, there is a tipping point to be aware of where:

  • the complexity of data flows piles up
  • the same data is being rendered in multiple places
  • the number of state changes blow out

Being able to tackle these problems in a single place is where Redux fits in.

The Problem

Imagine a fairly deep component hierarchy, starting with your top level App component. Deep down the tree, there are two child components that need to access a common piece of data (e.g. customer data). How should these components access the data they require?

Option 1 lift the state

This option involves placing the state (customer data) to the common ancestor in the component tree. In the worst case, this could be the top level component. It then becomes the burden of each decendent component to pass this state down as props (known as props drilling).

In a large app, this sucks.

  • keeping track of what props are being passed around
  • introducing new components between an existing prop drilling path

Option 2 react context

React context exposes global data and functions from a given React component. To access this published state, a downstream component imports the context and consumes.

The common ancestor parent component, could publish customer data (and functions if desired) via CustomerContext.Provider.

The child components could consume this data via CustomerContext.Consumer.

Option 3 Redux

Provides a centralised store (like a local client side DB).

Any component can connect and query the store.

To change (mutate) data in the store is not directly possible. Instead the requesting component dispatches an action, for example Create Customer, and the data is eventually updated/created. Any components connected to this data, automatically receieve it’s freshest version.

Container vs Presentation Components

Container (aka smart, stateful or controller view) components can be thought of as the backend of the frontend. They’re concerned with behaviour and marshalling. They’re typically stateful, as they aim to keep the child components fresly rendered with the latest data. A good container component should have very minimal (ideally zero) markup.

Presentation (dumb, stateless or view) components are purely concerned with markup, and contain almost no logic. They are dumb.

Container Presentation
How things work How things look
No markup All markup
Stateful Stateless
Aware of Redux Unaware of Redux
Pass data and actions down Receive data and actions with props
Subscribe to Redux state Read data from props
Dispatch Redux actions Invoke callbacks on props

When you notice that some components don’t use props they receive but merely forward them down…it’s a good time to introduce some container components — Dan Abramov

The Redux Principles

  1. One immutable store. It cant be changed directly. Immutability aids debugging, supports server rendering, and opens up the ability to easily centralise state functionality such as undo and redo.
  2. Actions trigger changes. Components that wish to change state, do so by dispatching an action.
  3. State changes are handled by pure functions, known as reducers.

Conceptually this is publish subscribe. The redux flow visually:

+---------+
| ACTION  |
+----+----+
     |
     v
+----+----+       +-----------+
|  STORE  +<----->+  REDUCER  |
+----+----+       +-----------+
     |
     v
+----+----+
|  REACT  |
+---------+

Actions

An action represents the intent via its type property, the only mandatory field. Other data can be encoded into the action however you please.

{ type: RATE_GYM, rating: 4 }

In this example, rating could be a complex object, a boolean, or there could be several more properties in addition to rating.

Typically actions are made with a factory method known as an action creator:

rateGym(rating) {
  return { type: RATE_GYM, rating: rating }
}

This is good practice as the consuming components don’t need to know about the internals of action structures. For each entity type, its common to have a CRUD (create, retrieve, update, delete) set of action creators.

The Store

A store is created in the entry point of your application (i.e. as it starts up):

let store = createStore(reducer);

The store API is suprisingly simple:

  • store.dispatch(action)
  • store.subscribe(listener)
  • store.getState()
  • replaceReducer(nextReducer)

Immutability

A cornerstone principle of redux, is that state is never modified (mutated), instead a completely new copy of the data is returned.

Benefits of immutable state:

  • Clarity around what is responsible for state changes, everything has a clearly defined reducer. No more debugging trying to track down the piece of code responsible for mutating the state.
  • Performance the job of Redux determining if a state change has occured and notifying React is greatly simplified with an immutable store. Instead of doing complex field level comparisons of each data element before and after, all Redux needs to do is a prevStoreState !== storeState check.
  • Unrivalled debugging because state is never mutated, allows for some incredible innovation in the debugging experience, such as time-travel debugging, undo/redo, skipping individual state actions, replaying the state interactions back. The redux devtools extensions opens this up.

Building complex objects up by hand each time a modification is needed, is not practical. JS does provide a number of ways to copy an object:

  • Object.assign shallow copy. Object.assign({}, state, { role: 'admin' }) assigns a new empty object based on the existing state object, after mixing-in the new role property.
  • The spread operator, whatever is placed on the right is shallow copied const newState = { ...state, role: 'admin' }
  • Immuatable friendly Array methods, such as map, filter, reduce, concat and spread. Avoid push, pop and reverse which mutate.

There is a large ecosystem of libraries available (immer, seamless-immutable, react-addons-update, Immutable.js) for working with data in an immutable compatable way, one popular option is immer:

Create the next immutable state tree by simply modifying the current tree

import produce from "immer";

const user = {
  name: "Benjamin",
  address: {
    state: "New South Wales",
  },
};

const userCopy = produce(user, (draftState) => {
  draftState.address.state = "Victoria";
});

console.log(user.address.state); // New South Wales
console.log(userCopy.address.state); // Victoria

Options for enforcing immutability:

  1. Trust the development team, through training and coding practices.
  2. Warn whenever state is mutated using redux-immutable-state-invariant (only use in development!)
  3. Enforce with the use of a library such as immer, immutable.js or seamless-immutable.

Reducers

An action is eventually handled by a reducer. Metaphorically the reducer is the meat grinder of Redux, state goes in, state comes out.

function myReducer(state, action) {
  switch (action.type) {
    case "INCREMENT_COUNTER":
      //state.counter++; //BAD: never mutate
      //return state;
      return { ...state, counter: state.counter + 1 };
    default:
      return state;
  }
}

If and when the state is returned by a reducer, the store is updated.

Reducers must be pure. In other words a reducer must:

  • Never mutate state
  • Perform side effects, such as API calls or routing transitions
  • Call non-pure functions (e.g. Date.now, Math.random())

Therefore for a given input, and reducer is guaranted to alway return the same output.

When a dispatch is submitted, ALL reducers are invoked. That’s why its important for the untouched state to be returned as the default case of the switch statement.

A reducer should be independent and responsible for updates to a slice of state, however there are no hard and fast rules.

Each action can be handled by one or more reducers.

Each reducer can handle multiple actions.

Any React components that a glued up to the store are automatically updated, via a push notification using React-Redux.

React-Redux

Redux isn’t exclusive to React. With React-Redux, can tie React components to state in the Redux store.

Two problems it solves:

  • Attaching the app to the redux store (using a Provider component)
  • Creating container components (using the Connect function)

React-Redux Provider

Wrapping the root App component in a provider opens up the Redux store to every component in your application.

<Provider store={this.props.store}>
  <App />
</Provider>

React-Redux Connect

Before a container component is exported in the usual fashion, to connect it to the Redux store, it is wrapped with the connect function like so:

function mapStateToProps(state, ownProps) {
  return { authors: state.authors };
}

export default connect(mapStateToProps, mapDispatchToProps)(AboutPage);

mapStateToProps

mapStateToProps defines what state needs to be exposed as props.

It returns an object that defines the data of interest. Each property defined on the object magically becomes a prop on React component. Anytime this data changes in the store, Redux will automatically fire mapStateToProps.

The more specific you can be in mapStateToProps is a performance win, as it will cut down on the number of possible state change notifications Redux needs to manage.

Important everytime the component is updated, the mapStateToProps function is called. Eeek. If there is some heavy lifting going on, such as transforming or sorting a large data structure, consider memoization (like caching for functions), where if the exact same state is presented as previously done, you can simply re-use the previously calculated results.

Memoizing libraries, called selectors, like reselect exist to make this fun.

What do selectors bring to the table?

  • They are efficient. A selector is not recomputed unless one of its arguments changes.
  • The can compute derived data, allowing Redux to store the minimal possible state.
  • They are composable. They can be used as input to other selectors.

Here’s one in action:

const getAllCoursesSelector = (state) => state.courses;

export const getCoursesSorted = createSelector(
  getAllCoursesSelector,
  (courses) => {
    return [...courses].sort((a, b) =>
      a.title.localeCompare(b.title, "en", { sensitivity: "base" })
    );
  }
);

mapDispatchToProps

How you expose redux actions to your components.

mapDispatchToProps defines what actions do I want on props. It receives dispatch as its lone parameter, and returns the callback props you want to pass down.

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(actions, dispatch),
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(AboutPage);

Four ways to deal with mapDispatchToProps:

Option 1: Simply ignore it, and use dispatch directly in your component.

this.props.dispatch(loadProducts());

Option 2: Manually wrap calls to dispatch:

function mapDispatchToProps(dispatch) {
  return {
    loadProducts: () => {
      dispatch(loadProducts));
    },
    createProduct: (product) => {
      dispatch(createProduct(product));
    }
  }
}

To consume an action in the component becomes quite clean:

this.props.loadProducts();

Option 3: Use bindActionCreators

This ships with redux, and takes an array of actions. It will wrap each of them in a call to dispatch for you.

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(actions, dispatch),
  };
}

Option 4: Return objects

Declare mapDispatchToProps as an object, as opposed to a map. Redux connect will automatically wrap each action creator in dispatch.

const mapDispatchToProps = {
  incrementCounter,
};

A chat with redux

This dialogue from @housecor helps drive all the moving pieces home:

  • React: Hey CourseAction, someone clicked this “Save Course” button.
  • Action: Thanks React! I will dispatch an action so reducers that care can update state.
  • Reducer: Ah, thanks action. I see you passed me the current state and the action to perform. I’ll make a new copy of the state and return it.
  • Store: Thanks for updating the state reducer. I’ll make sure that all connected components are aware.
  • React-Redux: Woah, thanks for the new data Mr. Store. I’ll now intelligently determine if I should tell React about this change so that it only has to bother with updating the Ul when necessary.
  • React: Ooo! Shiny new data has been passed down via props from the store! I’ll update the Ul to reflect this!