By Marc Garreau and Will Faurot

If you’re interested in Redux, but don’t know a lot about it, this article gives an introduction to Redux and what it does in web development.

Save 37% off Redux in Action with code fccgarreau at manning.com.


What is state?

React components have the concept of local or component state. Within any given component, you can keep track of the value of an input field or whether a button has been toggled, for example. Local state makes easy work of managing a single components behavior. Today’s single page applications often require synchronizing a complex web of state. Nested levels of components may render a different user experience based on the pages a user already visited, the status of an AJAX request, or whether a user is logged in.

Let’s consider a use case involving the authentication status of a user. Your product manager tells you that when a user is logged into an ecommerce store, the navigation bar should display the user’s avatar image, the store should display items nearest to the user’s zip code first, and the newsletter signup form should be hidden. Within a vanilla React architecture, your options are limited for syncing state across each of the components. In the end, you’ll likely end up passing the authentication status and additional user data from one top-level component down to each of these nested components.

This architecture has several disadvantages. Along the way, data may filter through components which have no use other than to pass the data on to their children. In a large application, this can result in tons of data moving through unrelated components, passed down via props or passed up via callbacks. It is likely that a small number of components at the top of the application end up with an awareness of most of the state used throughout the entire application. At a certain scale, maintaining and testing this code becomes untenable. Because React wasn’t intended to solve the same breadth of problems that other MVC frameworks attempted to address, an opportunity existed to bridge those gaps.

With React in mind, Facebook eventually introduced Flux, an architecture pattern for web applications. Flux became tremendously influential in the world of front-end development and began a shift in how people think about state management in client-side applications. Facebook offered its own implementation of this pattern, but soon more than a dozen “Flux-inspired” state management libraries emerged and competed for React developers’ attention.

This was a tumultuous time for React developers looking to scale an application. We saw the light with Flux, but continued to experiment to find more elegant ways to manage complex state in applications. For a time, newcomers encountered a paradox of choice; a divided community effort produced many options, and it was anxiety-inducing. To our surprise and delight though, the dust is already settling and Redux has emerged as a clear winner.

Redux took the React world by storm with a simple premise, a big payoff, and a memorable introduction. The premise is to store your entire application state in a single object using pure functions. The payoff is a totally predictable application state. The introduction, for most early users, came in Dan Abramov’s 2015 React Europe conference talk, titled “Live React: Hot Reloading with Time Travel.” Dan wowed attendees by demonstrating a Redux developer experience that blew established workflows out of the water. A technique called “hot loading” made live application updates while maintaining existing state, and his nascent Redux developer tools enabled you to “time travel” through application state — rewinding and replaying user actions with a single click. The combined effect offers developers debugging super powers.

To understand Redux, we’d first like to properly introduce you to Flux, the architecture pattern developed at Facebook and credited to Jing Chen. Redux and many of its alternatives are variations of this Flux architecture.


What is Flux?

Flux is foremost an architecture pattern. It was developed as an alternative to the prevailing Model-View-Controller (MVC) JavaScript patterns popularized by incumbent frameworks, such as Backbone, Angular, or Ember. Although each framework puts its own spin on the MVC pattern, many share similar frustrations: generally, the flow of data between models, views and controllers can be difficult to follow.

Many of these frameworks use two-way data binding, in which changes to the views update corresponding models, and changes in the models update corresponding views. When any given view can update one or more models, which in turn can update more models, you can’t be blamed for losing track of the expected outcome at a certain scale. Chen contested that although MVC frameworks work well for smaller applications, the two-way data-binding models that many of them employ don’t scale well enough for the size of Facebook’s application. Developers at the company became apprehensive of making changes, for fear of the tangled web of dependencies producing unintended consequences.

Flux sought to address the unpredictability of state and the fragility of a tightly coupled model and view architecture. Chen went about scrapping the two-way data binding model in favor of a unidirectional data flow. Instead of permitting each view to interact with their corresponding models, Flux requires all changes to state to follow a single path. When a user clicks a Submit button on a form, for example, an action is sent to the application is the one and only dispatcher. The dispatcher sends the data through to the appropriate data stores for updating. Once updated, the views become aware of the new data to render. Figure 1 illustrates this unidirectional data flow.


Figure 1. Flux specifies that data must flow in a single direction.


Actions

Every change to state starts with an action (figure 1). An action is an object describing an event in your application. They’re typically generated by either a user interaction or by a server event, like an HTTP response.

Dispatcher

All data flow in a Flux application is funneled through a single dispatcher. The dispatcher has little functionality, because its purpose is to receive all actions and send them to each registered store. Every action is sent to every store.

Stores

Each store manages the state of one domain within an application. In an ecommerce site, you may expect to find a shopping cart store and a product store, for example. Once a store is registered with the dispatcher, it begins to receive actions. When it receives an action type that it cares about, the store updates accordingly. Once a change to the store is made, an event is broadcast to let the views know to update using the new state.

Views

Flux may have been designed with React in mind, but the views aren’t required to be React components. For their part, the views need only subscribe to the stores from which they wish to display data. The Flux documentation encourages the use of the controller-view pattern, whereby a top-level component handles communication with the stores and passes data to child components. Having both a parent and a nested child component communicating with stores can lead to extra renders and unintended side-effects.

Again, Flux is an architecture pattern first. The Facebook team maintains one simple implementation of this pattern, aptly (or confusingly, depending on your perspective) named Flux. Many alternative implementations have emerged since 2014, including Alt, Reflux, and Redux.


What is Redux?

We can’t put it much better than the official docs: “Redux is a predictable state container for JavaScript applications.” It’s a standalone library, but it’s used most often as a state management layer with React. Like Flux, its major goal is to bring consistency and predictability to the data in our applications. Redux divides the responsibilities of state management into a few separate units:

  • The store holds all of your application state in a single object. (We’ll commonly refer to this object as the state tree.)

  • The store can be updated only via actions, an object describing an event.

  • Functions known as reducers specify how to transform application state. Reducers are functions that take the current state in the store and an action, and return the next state after applying any updates.

Technically speaking, Redux may not qualify as a Flux implementation. It nontrivially deviates from some of the components of the prescribed Flux architecture, such as the removal of the dispatcher. Ultimately though, Redux is Flux-like and the distinction is a matter of semantics.

Redux enjoys the benefits of a predictable data flow from the Flux architecture, but also found ways to alleviate the uncertainty of store callback registrations. As alluded to in the previous section, it can be a pain to reconcile the state of multiple Flux stores. Redux, instead, prescribes a single store to manage the state of an entire application.

React and Redux

Although Redux was designed and developed in the context of React, the two libraries are completely decoupled. React and Redux are connected via bindings, shown in figure 2.


Figure 2. Redux isn’t part of any existing framework or library, but additional tools called bindings connect Redux with React. You can use the react-redux package for this.


It turns out that the Redux paradigm for state management can be implemented alongside most JavaScript frameworks. Bindings exist for Angular 1 and 2, Backbone, Ember, and many more technologies.

Redux is small standalone library, but it fits particularly well with React components. Redux helps you define what your application does; React handles how your application looks.

Most of the code you’ll write falls into a few categories:

  • The application’s state and behavior, handled by Redux.

  • Bindings, provided by the react-redux-package, that connects the data in the Redux store with the view (React components).

  • Stateless components that comprise much of your view layer.

You’ll find that React is a natural ecosystem for Redux. While React has mechanisms to manage state directly in components, the door is wide open for Redux to come in and manage the greater application state.

The Three Principles

You’ll cover substantial ground by grokking the fact that state in Redux is represented by a single source of truth, is read-only, and changes to it must be made with pure functions.

Single source of truth

Unlike the various domain stores prescribed by the Flux architecture, Redux manages an entire application’s state in one object tree, inside of one store. The use of a single store has important implications. The ability to represent the entire application state in a single object simplifies the developer experience; it becomes dramatically easier to think through the application flow, predict the outcome of new actions, and debug issues produced by any given action. The potential for time travel debugging, or the ability flip back and forth through snapshots of application state, is what inspired the creation of Redux in the first place.

State is read-only

As with Flux, actions are the only way to initiate changes in application state. No stray AJAX call can produce a change in state without being communicated via an action. Redux differs from many Flux implementations though, in that these actions don’t result in a mutation of the data in the store. Instead, each action results in a shiny new instance of the state to replace the current one. More on that subject in the next bullet point.

Changes are made with pure functions

Actions are received by reducers. It’s important that these reducers be pure functions. Pure functions are deterministic; they always produce the same output given the same inputs, and they don’t mutate any data in the process. If a reducer were to mutate the existing state while producing the new one, we may end up with erroneous new state, but we also lose the predictable transaction log that each new action’s intended to provide. The Redux developer tools and other features, such as undo and redo functionality, rely on application state being computed by pure functions.

The Workflow

This far we’ve touched briefly upon things like actions, reducers, and the store, but in this section, we’ll cover each in more depth. What’s important to take away here is the role that each element plays, and how they work together to produce a desired result. For now, don’t worry about finer implementation details.

Modern web applications are ultimately about handling events. They could be initiated by a user, like navigating to a new page or submitting a form. Or they could be initiated by another external source, like a server response. Responding to events usually involves updating state and re-rendering with that updated state. The more your application does, the more state you need to track and update. Combine this with the fact that most of these events occur asynchronously, and you suddenly have some real obstacles to maintaining an application at scale.

Redux exists to create structure around how you handle events and manage state in your application, hopefully making you a more productive and happy human in the process. Let’s look at how we might handle a single event in an application using Redux and React. Say you’re tasked with implementing one of the core features of a social network, adding a post to your activity feed. Here’s a quick mockup of a user profile page, which may or may not take its inspiration from Twitter.


Figure 3. A simple mockup of a profile page. This page is backed by two main pieces of data: the total post count, and the list of post objects in the user’s activity feed.


The following distinct steps are involved in handling an event like a new post:

  • From the view, indicate that an event has occurred (a post submission) and pass along the necessary data (the content of the post to be created).

  • Update state based on the type of event — add an item to the user’s activity feed and increment the post count.

  • Re-render the view to reflect the updated state.

Sounds reasonable, right? If you’ve used React before, you’ve likely implemented features like this directly into components. Redux takes a different approach. Code to satisfy the three tasks is moved out of React components into a few separate entities. We’re already familiar with the View in figure 4, but we’re excited to introduce a new cast of characters that you’ll hopefully learn to love.


Figure 4. A look at how data flows through a React/Redux application. We’ve omitted a few common pieces like middleware and selectors.


Actions

We want to do two things in response to the user submitting a new post: add the post to the user’s activity feed and increment their total post count. After the user submits, we’ll kick off the process by dispatching an action. Actions are plain old JavaScript objects that represent an event in your application, as follows:

  
 { 
   type: ‘CREATE_POST’,
   payload: {
     body: ‘All that is gold does not glitter’
   }
 }
  

Let’s break that down. We’ve an object with two properties:

  • type — a string that represents the category of action being performed. By convention, this property is capitalized and uses underscores as delimiters.

  • payload — an object that provides the data necessary to perform the action. In our case, we only need one field: the contents of the message we want to post.

Actions have the advantage of serving as audits. A historical record of everything happening in your application is saved, including any data needed to complete a transaction. It’s hard to understate how valuable this is in maintaining a grasp on a complex application. Once you get used to having a highly readable stream describing the behavior of your application in real-time, you’ll find it hard to live without.

You can think of Redux as decoupling what happens in an application from how we respond to an event. Actions handle the what in this equation. They describe an event, but they don’t know and don’t care what happens downstream. Somewhere down the road we’ll eventually need to specify how to handle an action. Sounds like a job fit for a reducer!

Reducers

Reducers are functions responsible for updating your state in response to actions. They’re simple functions that take your current state and an action as arguments, and return the next state.


Figure 5. An abstract representation of a reducer is function signature. If this diagram looks simple, it’s because it is. Reducers are meant to be simple functions that compute a result, making them easy to work with and test.


Reducers are typically easy to work with. As with all pure functions, they produce no side-effects. They don’t affect the outside world in any way, and they’re referentially transparent. The same inputs always yield the same return values. This makes them particularly easy to test. Given certain inputs, you can verify that you receive the expected result. Here’s how our reducer might update the list of posts and the total post count:


Figure 6. Visualizing a reducer hard at work. It accepts as input an action and the current state. The reducer must specify what the new state should look like. No mutations, no side-effects, no funny business–Data in, data out.


We’re focusing on a single event in this example, which means we need only one reducer. You certainly aren’t limited to only one. In fact, more sizable applications frequently implement several reducer functions, each concerned with a different slice of the state tree. These reducers are combined, or composed, into a single “root reducer.”

Store

Reducers describe how to update state in response to an action, but can’t modify state directly. That privilege rests solely with the store. In Redux, application state is stored in a single object. The store has a few main roles, which follow:

  • Hold application state.

  • Provide a way to access state.

  • Provide a way to specify updates to state. The store requires an action be dispatched to modify state.

  • Allow other entities to subscribe to updates (React components in our case). View bindings provided by react-redux allows us to receive updates from the store and respond to them in our components.

The reducer processed the action and computed the next state. Now it’s time for the store to update itself and broadcast the new state to all registered listeners (we care specifically about the components that make up our profile page).


Figure 7. The store now completes the loop by providing the new state to our profile page. Notice that the post count has been incremented, and the new post has been added to the activity feed. If our user adds another post, we’d follow the same exact flow. The view dispatches an action, reducers specify how to update state, and the store broadcasts the new state back to the view.


To review, an interaction with a view may produce an action. That action filters through one or more reducers and produces a new state tree within the store. Once the state updates, the views are made aware that there is new data to render. This is the whole cycle! Items in this diagram with a dashed border (action creators, middleware, and selectors) are optional, but powerful tools in a Redux architecture.

If this feels like a lot, don’t fret. If you’re new to the kind of one-directional architecture that we’re beginning to explore, it can be initially overwhelming (we certainly thought so at first). It takes time to let these concepts sink in. Developing a sense for what role they play and what type of code belongs where, is as much art as it is science, and it’s a skill you’ll develop over time as you continue get your hands dirty.

For more, read the free first chapter of Redux in Action on liveBook and see this slide deck.