|From React in Action by Mark T. Thomas
This article is about Flux application architecture and where Redux fits in.
Save 37% on React in Action. Just enter code fccthomas into the discount code box at checkout at manning.com.
The Flux application architecture
Modern applications must do more than they’ve ever had to before and are correspondingly more complex — internally and externally. Developers have long been aware of the mess that can be made of a complex application that grows without coherent design patterns in place. Spaghetti-code codebases aren’t only not fun to work with, but they slow developers and business units down. Remember the last time you worked in a large codebase full of one-off solutions and jQuery plugins? It probably was not fun. To combat disorganization, developers have developed paradigms like MVC (Model View Controller) to organize the functionality of an application and guide development. Flux (and, by extension, Redux) is an effort in the same vein that helps you deal with increased complexity in an application.
Don’t worry if you’re not familiar with the MVC paradigm; we won’t spend much time getting into it. But before we talk about Flux and Redux it might be helpful to briefly discuss it for the sake of comparison. If you’re interested in learning more about MVC, Jeff Atwood has some helpful thoughts at https://blog.codinghorror.com/understanding-model-view-controller/ and there are many other resources available online.
- Model: the data for your application. Usually a noun like “User,” “Account,” or “Post,” for example. Your model should have the basic methods to manipulate associated data. In the most abstract sense, the model represents raw data or knowledge. It’s where the data intersects with our application code. For example, the database might store several properties like “accessScopes” or “authenticated.” But the model is able to use this data for a method like isAllowedAccessForResource() which acts on the underlying data to model. The model is where raw data converges with our application code.
- View: a representation of your model. The view is often the user interface itself. The view shouldn’t have any logic in it which isn’t related to presentation of the data. For front-end frameworks, this generally means that a particular view was directly associated with a resource and would have CRUD (create, read, update, delete) actions associated with it. This isn’t how most front-end applications are built anymore.
- Controller: controllers are the “glue” that bind the model and view together. Controllers should usually be “only” glue, and not much more (for example, they shouldn’t have complex view or database logic in them). You should generally expect controllers to have far less ability to mutate data than the models they interact with.
The paradigms that we’re going to focus on in this article (Flux and Redux) depart from these concepts but still have the goal of helping you create a scalable, sensible, and effective application architecture.
Redux owes its origin and design to a pattern popularized at Facebook, Flux. If you’re familiar with the popular MVC pattern that Ruby on Rails and other application frameworks use, Flux might be a departure from what you’re used to. Rather than breaking parts of your application into models, views, and controllers, Flux defines several different parts to it:
- Store: Stores contain the application state and logic; they’re like a Model in a traditional MVC, but instead of representing a single database record, they manage the state of many objects. Unlike a model, we represent the data however makes sense, unconstrained by resources.
- Actions: rather than updating state directly, Flux apps modify their state by creating actions that modify state
- View: The user interface, usually React, but Flux doesn’t require React
- Dispatcher: A central coordinator of actions and updates to stores
Figure 1. A simple Flux overview. In the Flux pattern, actions are created from views. This might be a user clicking on something. From there, the dispatcher handles incoming actions. Actions are then sent to the appropriate store to update state. State, having changed, notifies a view that new data should be used (if applicable). Notice how this differs from a typical MVC-style framework where the view and a model (like the store here) are both be able to update the other. This “bidirectional” data flow differs from the more “unidirectional” flow typical in Flux architectures. Lastly, note that middleware is missing here: although possible to create in Flux, it’s less of a first-class citizen than in Redux and we omit it here.
If some of these parts sound familiar, the way data flows in Flux might not be if you’ve worked with MVC-style applications before. Data flows in a more unidirectional manner in the Flux paradigm, which differs from the bidirectional manner MVC-type implementations enforce. This usually means that there’s no single place in an app that data flows from; many different parts of the system have authority to modify state and state is often decentralized throughout the application. This approach works well in many cases, but in larger apps it can be confusing to debug and work with.
Think what this might look like in a medium-to-large application. Say you’ve a collection of models (user, account, authentication) which are associated with their own controllers and views. At any given place in the application, it could be difficult to pin down the exact location of state because it’s distributed across parts of the application (information about a user could be found on any of the three models we mentioned).
This might not necessarily be a problem for smaller apps and can even be made to work well for larger applications, but it can become more difficult in non-trivial client-side applications. For example, what happens when things you need to modify a model is use in fifty different locations and sixty different controllers need to know about changes to state? Making matters more complicated is views sometimes act like models in some front-end frameworks (state is even more decentralized). Where’s the source of truth for your data? If it’s spread across views, many different models, and all in a moderately complex setup, mentally keeping track of everything is going to be difficult. This can also result in inconsistent application state that causes application bugs, and it’s not a “developer-only” problem, end-users are directly affected as well.
Part of the reason for why this is difficult is that people generally aren’t good at reasoning about change that occurs over time. To drive this home, imagine a checkers board in your head. It’s not hard to hold maybe one or a few snapshots of the board in your head. But could you keep track of every snapshot of the board after twenty turns? Thirty? The entire game? We should be building systems which are easier for us to think about and use because keeping mental track of asynchronous changes to data over time is hard. For example, think of calling a remote API and using the data to update your application state. Simple for a handful of cases, but what if you need to call fifty different endpoints and need to keep track of the incoming responses while a user is still using the app and making changes that could result in more API interaction? It can be hard to mentally line them all up in a row and predict what the result of changes would be.
You might notice some similarities already between React and Flux. They’re both relatively new approaches to building user interfaces and they both aim to improve the mental model a developer works with. In each, changes should be easy to reason about and you should be able to build your UI in a way that empowers, not in a way that hinders you.
What does Flux look like in code? It’s primarily a paradigm, and there are plenty of libraries available that implement the core ideas of Flux. They all slightly differ from one another in how they implement Flux. Redux does this, too, even though it’s particular flavor of Flux gained the most use and mindshare. Other Flux libraries include Flummox, Fluxxor, Reflux, Fluxible, Lux, McFly, and MartyJS (though in practice you’ll see little use of these compared to Redux).
Meet Redux: a variation on Flux
Nailing down exact definitions of what is and isn’t considered Flux isn’t important here, but we should cover some of the import differences between the Flux and Redux paradigms, including
- Redux uses a single store: rather than locating state information in multiple stores across the app, Redux apps keep everything in one place. In Flux, you can have many different stores. Redux breaks from this and enforces a single global store.
- Redux introduces reducers: a more immutable approach to mutation (“reducers”): in Redux, state is changed in a predictable and deterministic way, one part of state at a time and only in one place (the global store).
- Redux introduces middleware: because actions and data flow in a unidirectional way, you can add middleware to your Redux app and inject custom behavior as data is updated.
- Redux actions are decoupled from the store: action-creators don’t dispatch anything to the store; instead, they return action objects that a central dispatcher uses
These might be subtle differences to you, and this is ok — our goal is learning about Redux and not doing a “spot the differences” exercise. Figure 2 shows an overview of the Redux architecture. We’ll dive into each of the different sections, explore how they work, and develop a Redux architecture for our app.
Figure 2. An overview of Redux. Actions, a store, and reducers make up the bulk of the Redux architecture. Redux uses a single, centralized state object which is updated in specific, deterministic ways. An action is created when we want to update state (usually due to an event like a click). The action has a type that a certain reducer handles. The reducer that handles the given action type makes a copy of the current state, modify it with data from the action, and then returns the new state. When the store is updated, view layers (React in our case) can listen to updates and respond accordingly. Also note that in the figure the views are reading in updates from the store; they don’t care about the data being communicated to them. The React-redux library handles passing new props to components when the store changes, but views still receive and display data.
Getting set up for Redux
Redux is a paradigm for your application architecture, but it’s also a library that you can install. This is one area where Redux shines over a “raw” Flux implementation. Numerous implementations of the Flux paradigm exist — Flummox, Fluxxor, Reflux, Fluxible, Lux, McFly, MartyJS, to name a few — and they all have varying degrees of community support and different APIs. Redux enjoys strong community support, but the Redux library has a small, powerful API that helped it become one of the most popular and relied-upon libraries for React application architecture. In fact, it’s common enough to see Redux used with React that the core teams for each library often interact with each other and ensure compatibility and feature-awareness. Some people are on both teams, and there’s generally great visibility and communication between the projects.
To get set up to use Redux, we’re going to need to do a few things:
- Make we have run npm install with the source code from the article (found here: https://github.com/react-in-action/letters-social) to ensure all the right dependencies are installed locally.
- install the Redux developer tools. It allows us to inspect the Redux store and actions in the browser.
Redux is predictable by design and that makes it easier to create some amazing debugging tools. Engineers like Dan Abramov and others who work on the Redux and React libraries have helped create some powerful tools for working with Redux applications. Because the state in Redux changes in predictable ways, debugging in new ways is possible: you can track individual changes to your app state, inspect diffs between changes, and even rewind and replay your app state over time. The Redux Dev Tools extension lets you do all this and more and comes bundled as a browser extension. To install it for your browser, follow the instructions at https://github.com/zalmoxisus/redux-devtools-extension. Figure 3 shows a sneak-peak of what’s available with the Redux Dev Tools.
Figure 3. The Redux Dev Tools extension bundles the popular Redux Dev Tools library from Dan Abramov in a convenient browser extension. With it, you can rewind and replay your Redux app, inspect changes one by one, examine diffs between changes in state, review your entire app state in one area, generate testing boilerplate, and more.
After installing the extension, you should see the new Dev Tools icon in your browser toolbar. As of the time of writing, it only appears colorized when it detects a Redux app instance in development mode, and if you visit the app or other sites that don’t have Redux set up the extension won’t work yet. But once we configure the app, you’ll see the icon appear with color and clicking on it opens the tools.
And that’s how you can get started using Redux.
If you’re interested in learning more about the book, check it out on liveBook here and see this Slideshare Presentation.