Redux Book
Redux Book
Redux Book
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.
Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Code Repository . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Chapter 5. WebSockets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Basic Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Redux Link . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Code Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Complete WebSocket Middleware Code . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
Chapter 6. Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Test Files and Directories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Testing Action Creators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Async Action Creators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Reducer Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Testing Middleware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Integration Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
What Is Flux?
Before diving into Redux, we should get familiar with its base and predecessor, the Flux architecture.
Flux is a generic architecture or pattern, rather than a specific implementation. Its ideas were
first introduced publicly by Bill Fisher and Jing Chen at the Facebook F8 conference in April 2014.
Flux was touted as redefining the previous ideas of MVC (ModelViewController) and MVVM
(ModelViewViewModel) patterns and two-way data binding introduced by other frameworks by
suggesting a new flow of events on the frontend, called the unidirectional data flow.
In Flux events are managed one at a time in a circular flow with a number of actors: dispatcher,
stores, and actions. An action is a structure describing any change in the system: mouse clicks,
timeout events, Ajax requests, and so on. Actions are sent to a dispatcher, a single point in the
system where anyone can submit an action for handling. The application state is then maintained
in stores that hold parts of the application state and react to commands from the dispatcher.
http://www.businessinsider.com/these-10-inventions-were-made-by-mistake-2010-11?op=1&IR=T
http://survivejs.com/blog/redux-interview/
https://twitter.com/acdlite
http://elm-lang.org
http://redux.js.org/
Chapter 1. Core Concepts of Flux and Redux 7
Flux overview
This flow ensures that its easy to reason about how actions flow in the system, what will cause the
state to change, and how it will change.
Consider an example from a jQuery or AngularJS application. A click on a button can cause multiple
callbacks to be executed, each updating different parts of the system, which might in turn trigger
updates in other places. In this scenario it is virtually impossible for the developer of a large
application to know how a single event might modify the applications state, and in what order
the changes will occur.
In Flux, the click event will generate a single action that will mutate the store and then the view. Any
actions created by the store or other components during this process will be queued and executed
only after the first action is done and the view is updated.
Facebooks developers did not initially open-source their implementation of Flux, but rather released
only parts of it, like the dispatcher. This caused a lot of open-source implementations to be built by
the community, some of them significantly different and some only slightly changing the original
Chapter 1. Core Concepts of Flux and Redux 8
patterns. For example, some moved to having multiple dispatchers or introduced dependencies
between stores.
To better understand Redux, lets imagine an application that helps us manage a recipe book. The
Redux store is where the recipe book itself will be saved, in a structure that might be a list of recipes
and their details.
The app will allow us to perform different actions like adding a recipe, adding ingredients to a
recipe, changing the quantity of an ingredient, and more. To make our code generic, we can create a
number of services. Each service will know how to handle a group of actions. For example, the book
service will handle all the add/remove recipe actions, the recipe service will handle changing recipe
information, and the recipe-ingredients service will handle the actions to do with ingredients. This
will allow us to better divide our code and in the future easily add support for more actions.
To make it work, our store could call each of our services and pass them two parameters: the current
recipe book and the action we want to perform. Each service in turn will modify the book if the
action is one it knows how to handle. Why pass the action to all the services? Maybe some actions
https://github.com/voronianski/flux-comparison
Chapter 1. Core Concepts of Flux and Redux 9
affect more than one service. For example, changing the measurements from grams to ounces will
cause the ingredients service to recalculate the amounts and the recipe service to mark the recipe as
using imperial measurements. In Redux, these services are the reducers.
We might want to add another layer, the middleware. Every action will be first passed through a list
of middleware. Unlike reducers, middleware can modify, stop, or add more actions. Examples might
include: a logging middleware, an authorization middleware that checks if the user has permissions
to run the action, or an API middleware that sends something to the server.
This simple example shows the base of Redux. We have a single store to control the state, actions to
describe the changes we want to make, reducers (services, in our example) that know how to mutate
the state based on the requested action, and middleware to handle the housekeeping tasks.
What makes Redux special, and sometimes hard to understand, is that reducers never change the
state (in our case, the recipe book), since it is immutable. Instead, the reducers must create a new
copy of the book, make the needed changes to the copy, and return the new, modified book to the
store. This approach allows Redux and the view layers to easily do change detection. In later chapters
we will discuss in detail why and how this approach is used.
It is important to note that the whole application state is kept in a single location, the store. Having a
single source of data provides enormous benefits during debugging, serialization, and development,
as will become apparent in the examples in this book.
Redux overview
Chapter 1. Core Concepts of Flux and Redux 10
Redux Terminology
1 {
2 type: 'INCREMENT',
3 payload: {
4 counterId: 'main',
5 amount: -10
6 }
7 }
Since these objects might have some logic and be used in multiple places in an application, they are
commonly wrapped in a function that can generate the objects based on a parameter:
As these functions create action objects, they are aptly named action creators.
Reducers
Once an action is sent to the store, the store needs to figure out how to change the state accordingly.
To do so, it calls a function, passing it the current state and the received action:
Chapter 1. Core Concepts of Flux and Redux 11
This function is called a reducer. In real Redux applications, there will be one root reducer function
that will call additional reducer functions to calculate the nested state.
Reducers never modify the state; they always create a new copy with the needed modifi-
cations.
Middleware
Middleware is a more advanced feature of Redux and will be discussed in detail in later chapters.
The middleware act like interceptors for actions before they reach the store: they can modify the
actions, create more actions, suppress actions, and much more. Since the middleware have access to
the actions, the dispatch() function, and the store, they are the most versatile and powerful entities
in Redux.
Store
Unlike many other Flux implementations, Redux has a single store that holds the application
information but no user logic. The role of the store is to receive actions, pass them through all
the registered middleware, and then use reducers to calculate a new state and save it.
When it receives an action that causes a change to the state, the store will notify all the registered
listeners that a change to the state has been made. This will allow various parts of the system, like
the UI, to update themselves according to the new state.
Chapter 1. Core Concepts of Flux and Redux 12
General Concepts
Redux is about functional programming and pure functions. Understanding these concepts is crucial
to understanding the underlying principles of Redux.
Functional programming has become a trendy topic in the web development domain lately, but it
was invented around the 1950s. The paradigm centers around avoiding changing state and mutable
datain other words, making your code predictable and free of side effects.
JavaScript allows you to write code in a functional style, as it treats functions as first-class objects.
This means you can store functions in variables, pass them as arguments to other functions, and
return them as values of other functions. But since JavaScript was not designed to be a functional
programming language per se, there are some caveats that you will need to keep in mind. In order
to get started with Redux, you need to understand pure functions and mutation.
1 function square(x) {
2 return x * x;
3 }
4
5 Math.sin(y);
6
7 arr.map((item) => item.id);
If a function uses any variables not passed in as arguments or creates side effects, the function is
impure. When a function depends on variables or functions outside of its lexical scope, you can never
be sure that the function will behave the same every time its called. For example, the following are
impure functions:
Chapter 1. Core Concepts of Flux and Redux 13
1 function getUser(userId) {
2 return UsersModel.fetch(userId).then((result) => result);
3 }
4
5 Math.random();
6
7 arr.map((item) => calculate(item));
Mutating Objects
Another important concept that often causes headaches for developers starting to work with Redux
is immutability. JavaScript has limited tooling for managing immutable objects, and we are often
required to use external libraries.
Immutability means that something cant be changed, guaranteeing developers that if you create an
object, it will have the same properties and values forever. For example, lets declare a simple object
as a constant:
1 const colors = {
2 red: '#FF0000',
3 green: '#00FF00',
4 blue: '#0000FF'
5 };
Even though the colors object is a constant, we can still change its content, as const will only check
if the reference to the object is changed:
1 colors = {};
2 console.log(colors);
3
4 colors.red = '#FFFFFF';
5 console.log(colors.red);
Try writing this in the developer console. You will see that you cant reassign an empty object to
colors, but you can change its internal value.
To make the colors object appear immutable, we can use the Object.freeze() method:
Chapter 1. Core Concepts of Flux and Redux 14
The value of the red property will now be '#FFFFFF'. If you thought that the value should be
'#FF0000', you missed that we changed the red property before we froze the object. This is a good
example of how easy it is to miss this kind of thing in real applications.
Here, once we used Object.freeze(), the colors object became immutable. In practice things
are often more complicated, though. JavaScript does not provide good native ways to make data
structures fully immutable. For example, Object.freeze() wont freeze nested objects:
To work around the nature of our beloved language, we have to use third-party libraries like deep-
freeze or ImmutableJS. We will talk about different immutable libraries later in the book.
The connection to different frameworks is done with the help of third-party libraries that provide
a set of convenience functions for each framework in order to seamlessly connect to Redux. The
library that we will use to connect Redux and React is called react-redux, and we will be covering
it extensively later in the book.
Using this structure, lets build a simple application that will implement a counter. Our application
will use pure JavaScript and HTML and require no additional libraries. We are going to have two
buttons that allow us to increment and decrement a simple counter, and a place where we can see
the current counter value:
1 <div>
2 Counter:
3 <span id='counter'></span>
4 </div>
5
6 <button id='inc'>Increment</button>
7 <button id='dec'>Decrement</button>
1 let state = {
2 counter: 3
3 };
To make our demo functional, lets create a click handler for each button that will use a dispatch()
function to notify our store that an action needs to be performed:
We will come back to its implementation later in this chapter. Also, lets define a function that will
update the counters value in the HTML based on application state received as an argument:
Since we want our view to represent the current application state, we need it to be updated every
time the state (and the counter) changes. For that, we will use the subscribe() function, which
we will also implement a bit later. The role of the function will be to call our callback every time
anything in the state changes:
1 subscribe(updateView);
We have now created a basic application structure with a simple state, implemented a function that
will be responsible for updating the HTML based on the state, and defined two magic functions
dispatch() and subscribe()to dispatch actions and subscribe to changes in state. But there is
Chapter 1. Core Concepts of Flux and Redux 17
still one thing missing. How will our mini-Redux know how to handle the events and change the
application state?
For this, we define an additional function. On each action dispatched, Redux will call our function,
passing it the current state and the action. To be compliant with Reduxs terminology, we will call
the function a reducer. The job of the reducer will be to understand the action and, based on it, create
a new state.
In our simple example our state will hold a counter, and its value will be incremented or decremented
based on the action:
1 // Our mutation (reducer) function creates a new state based on the action passed
2 function reducer(state, action) {
3 switch (action) {
4 case 'INC':
5 return { ...state, counter: state.counter + 1 };
6 case 'DEC':
7 return { ...state, counter: state.counter - 1 };
8 default:
9 return state;
10 }
11 }
An important thing to remember is that reducers must always return a new, modified copy of the
state. They shouldnt mutate the existing state, like in this example:
1 // This is wrong!
2 state.counter = state.counter + 1;
Later in the book you will learn how you can avoid mutations in JavaScript with and without the
help of external libraries.
Now its time to implement the actual change of the state. Since we are building a generic framework,
we will not include the code to increment/decrement the counter (as it is application-specific) but
rather will call a function that we expect the user to supply, called reducer(). This is the reducer
we mentioned before.
The dispatch() function calls the reducer() implemented by the application creator, passing it both
the current state and the action it received. This information should be enough for the reducer()
function to calculate a new state. We then check if the new state differs from the old one, and if it
does, we replace the old state and notify all the listeners of the change:
Chapter 1. Core Concepts of Flux and Redux 18
Again, it is very important to note that we expect a reducer to create a new state and not just modify
the existing one. We will be using a simple comparison by reference to check whether the state has
changed.
One remaining task is to notify our view of the state change. In our example we only have a single
listener, but we already can implement full listener support by allowing multiple callbacks to register
for the state change event. We will implement this by keeping a list of all the registered callbacks:
This might surprise you, but we have just implemented the major part of the Redux framework. The
real code isnt much longer, and we highly recommended that you take half an hour to read it.
https://github.com/reactjs/redux/tree/master/src
Chapter 1. Core Concepts of Flux and Redux 19
We will change our previous state definition to be a constant that only defines the initial value of
the state:
1 const initialState = {
2 counter: 3
3 };
As you can see, we are using our reducer from before. The only change that needs to be made to the
reducer is the switch statement. Instead of doing:
1 switch (action)
Changes to
1 switch (action.type)
The reason behind this is that actions in Redux are objects that have the special type property, which
makes reducer creation and action data more consistent.
The Redux store will also give us all the features we implemented ourselves before, like subscribe()
and dispatch(). Thus, we can safely delete these methods.
To subscribe to store changes, we will simply call the subscribe() method of the store:
Chapter 1. Core Concepts of Flux and Redux 20
1 store.subscribe(updateView);
Since subscribe() does not pass the state to the callback, we will need to access it via store.getState():
The last change is in the dispatch() method. As mentioned previously, our actions now need to
have the type property. Thus, instead of simply sending the string 'INC' as the action, we now need
to send { type: 'INC' }.
Chapter 1. Core Concepts of Flux and Redux 21
The HTML
The JavaScript
Summary
In this chapter we briefly covered the history of Redux and Flux, and learned how Redux works at its
core. We also learned a bit about basic functional programming principles, such as pure functions
and immutability. As they are very important for our real-world applications, we will talk about
these concepts more later in the book. In the next chapter we are going to see how to actually work
with Redux by building a simple recipe book application.
Chapter 2. Your First Redux
Application
In the previous chapter we learned what Redux is and how it works. In this chapter we will learn
how to use it for a simple project. The code base created in this chapter will be used as the base for
all the examples in the rest of this book. It is highly recommended that you follow along with and
fully understand this chapter and its code before moving on to more advanced topics.
Starter Project
Modern client-side applications often require a set of so-called boilerplate in order to make devel-
opment easier. The boilerplate may include things such as directory structure, code transformation
tools like SCSS and ES2016 compilers, testing infrastructure, and production pipeline tools for tasks
such as minification, compression, and concatenation.
To ease the chore of setting up a new project, the open-source community has created dozens of
different starter projects. The larger ones, like react-redux-starter-kit, consist of over a hundred
files. We will use a much simpler boilerplate, just enough to cover all the concepts explained in this
book.
As our project will be pure Redux, it will require no React or related libraries. We will use Webpack
as our main tool to handle all code transformation and production flow tasks.
Skeleton Overview
To start things off, lets clone the starter project, install the needed packages, and verify that our
environment is ready:
https://github.com/davezuko/react-redux-starter-kit
https://webpack.js.org/
Chapter 2. Your First Redux Application 24
If everything went smoothly, you should be able to access http://localhost:8080 and see a page
showing A simple Redux starter and a running counter. If you open the JavaScript console in the
Developer Tools, you should also see Redux started output. Our project is ready!
Time to open the code editor and go over the five files currently making up the project:
1. .gitignore A list of filename patterns for Git to ignore when managing our repository.
2. package.json A list of all properties of our project and packages used.
3. webpack.config.js Webpack configuration.
4. app/index.html The HTML entry point for the project.
5. app/app.js The JavaScript entry point to our code.
6. app/assets/stylesheets/main.css Some basic CSS for the sample project.
.gitignore
This is a special configuration file for Git version control system, this file instructs Git which files
and directories should not be managed by it (for example, node_modules).
package.json
While the majority of the fields in this file are irrelevant at this point, it is important to note two
sections, devDependencies and dependencies. The former is the list of all the tools needed to build
the project. It currently includes only webpack-tools and the Babel transpiler, required to transpile
ES2016. The dependencies section lists all the packages we will bundle with our application. It
includes only the redux library itself.
webpack.config.js
This is the main Webpack configuration file. This settings file instructs Webpack how to chain
transpile tools and how to build packages, and holds most of the configuration of our projects
tooling. In our simple project there is only one settings file (larger projects might have more granular
files for testing, development, production, etc.). Our webpack.config.js file sets up Babel to transpile
ES2016 into ES5 and defines the entry point of our application.
index.html / app.js
Single-page applications, unlike their server-generated cousins, have a single entry point. In our
project every part and page of the application will be rendered starting from index.html and all the
JavaScript-related startup code will be in app.js.
http://localhost:8080
https://git-scm.com/
Chapter 2. Your First Redux Application 25
Simple state
1 recipes = [
2 { name: 'Omelette' },
3 ...
4 ];
Ingredients for each recipe will contain a name and a quantity. Connecting them to the state will
be a bigger challenge. There are three general approaches to make this connection.
The nested objects approach is to hold the ingredients as an array inside a recipe itself:
1 state = {
2 recipes: [
3 {
4 name: 'omelette',
5 ingredients: [
6 {
7 name: 'eggs',
8 quantity: 2
9 }
10 ]
11 },
12 ...
13 ]
14 };
The nested reference approach is to store the recipe ingredient information directly in the state and
hold an array of recipe ingredient IDs in each recipe:
Chapter 2. Your First Redux Application 26
1 state = {
2 recipes: [
3 {
4 name: 'omelette',
5 ingredients: [2, 3]
6 }
7 ],
8 ingredients: {
9 2: {
10 name: 'eggs',
11 quantity: 2
12 },
13 3: {
14 name: 'milk',
15 quantity: 1
16 }
17 }
18 };
The separate object approach is to store the ingredients as a standalone array in the state, and put
the ID of the recipe the array is connected to inside of it:
1 state = {
2 recipes: [
3 {
4 id: 10,
5 name: 'omelette'
6 }
7 ],
8 ingredients: [
9 {
10 recipe_id: 10,
11 name: 'eggs',
12 quantity: 2
13 },
14 {
15 recipe_id: 10,
16 name: 'milk',
17 quantity: 1
Chapter 2. Your First Redux Application 27
18 }
19 ]
20 };
While all the approaches have their upsides and downsides, we will quickly discover that in Redux,
keeping the structure as flat and normalized as possible (as in the second and third examples shown
here) makes the code cleaner and simpler. The states structure implies the use of two separate
reducers for recipes and ingredients. We can process both independently.
The biggest difference between the second and third options is how the link is made (who holds the
ID of whom). In the second example, adding an ingredient will require an update in two different
parts of the statein both the recipes and ingredients subtreeswhile in the third approach, we
can always update only one part of the tree. In our example we will use this method.
The subject of state management is covered in detail in the State Management Chapter in
Part 2.
The createStore() function can receive a number of parameters, with only one being requiredthe
reducer. In our example, the reducer simply returns the same state regardless of the action.
To make things more interesting, we can provide a default state to the store. This is useful when
learning, but the real use of this feature is mainly with server rendering, where you precalculate the
state of the application on the server and then can create the store with the precalculated state on
the client.
Chapter 2. Your First Redux Application 28
In the last line we make the store globally available by putting it on the window object. If we go to
the JavaScript console, we can now try playing with it:
As you can see, we can use the store object to access the current state using getState(), subscribe
to get notifications on store changes using subscribe(), and send actions using dispatch().
Adding Recipes
To implement adding recipes, we need to find a way to modify our store. As we learned in the
previous chapter, store modifications can only be done by reducers in response to actions. This
means we need to define an action structure and modify our (very lean) reducer to support it.
Chapter 2. Your First Redux Application 29
Actions in Redux are nothing more than plain objects that have a mandatory type property. We will
be using strings to name our actions, with the most appropriate in this case being 'ADD_RECIPE'.
Since a recipe has a name, we will add it to the actions data when dispatching:
Lets modify our reducer to support the new action. A simple approach might appear to be the
following:
While this looks correct (and works when tested in our simple example), this code violates the basic
Redux principle of store immutability. Our reducers must never change the state, but only create a
new copy of it, with any modifications needed. Thus, our reducer code needs to be modified:
The 'ADD_RECIPE' case has become more complex but works exactly as expected. We are using
the Object.assign() method Object.assign({}, state, { key: value }) to create a new object
that has all the key/value pairs from our old state, but overrides the recipes key with a new value. To
calculate the list of new recipes we use concat() instead of push(), as push() modifies the original
array while concat() creates a new array containing the original values and the new one.
More information about the Object.assign() method is available in the Reducers Chapter.
Chapter 2. Your First Redux Application 30
Adding Ingredients
Similar to adding recipes, this step will require us to modify the reducer again to add support for
adding ingredients:
One problem you might encounter while dispatching actions from the console to test the store is
that its hard to remember the properties that need to be passed in the action object. This, among
other reasons, is why in Redux we use the concept of action creators: functions that create the action
object for us.
This function both hides the structure of the action from the user and allows us to modify the action,
setting default values for properties, performing cleanup, trimming names, and so on.
Chapter 2. Your First Redux Application 31
For more information on action creators, consult the Actions and Action Creators Chapter
in Part 3.
Final index.js
Multi-responsibility reducer
There are three main benefits here. First, our root reducer is now very simple. All it does is create a
new state object by combining the old state and the results of each of the subreducers. Second, our
recipes reducer is much simpler as it only has to handle the recipes part of the state. And best of
all, our root, ingredients, and any other reducers that we might create dont need to know or care
about the internal structure of the recipes subtree. Thus, changes to that part of the tree will only
affect the recipes part. A side effect of this is that we can tell each reducer how to initialize its own
subtree, by using default parameters from ES2016:
Here we created a root reducer that employs two subreducers, one sitting in the recipes subtree
and the other in the ingredients subtree. This is a good time to split our reducers into their own
files, reducers/recipes.js and reducers/ingredients.js.
constants/action-types.js
Now in our reducers and action creators we will use the constants instead of the strings:
Chapter 2. Your First Redux Application 34
Using constants
1 import { ADD_RECIPE } from 'constants/action-types';
2
3 const recipesReducer = (recipes = [], action) => {
4 switch (action.type) {
5 case ADD_RECIPE:
6
Simple UI
To get a feeling for how a simple UI can be connected to Redux, we will be using a bit of jQuery
magic. Note that this example is very simple and should never be used in a real application, although
it should give you a general feeling of how real applications connect to Redux.
Lets store our current UI in ui/jquery/index.js. The jQuery UI will create a simple view of current
recipes in the store:
ui/jquery/index.js
1 import $ from 'jquery';
2 import store from 'store/store';
3
4 function updateUI() {
5 const { recipes } = store.getState();
6 const renderRecipe = (recipe) => `<li>${ recipe.name }</li>`;
7
8 $('.recipes > ul').html(recipes.map(renderRecipe));
9 }
10
11 export default function loadUI() {
12 $('#app').append(`
13 <div class="recipes">
14 <h2>Recipes:</h2>
15 <ul></ul>
16 </div>
17 `);
18
19 updateUI();
20 }
We are using jQuerys append() method to add a new div to our application container and using the
updateUI() function to pull the recipes list from our state and display them as a list of unordered
elements.
Chapter 2. Your First Redux Application 35
To make our UI respond to updates, we can simply register the updateUI() function within our
store, inside loadUI():
1 store.subscribe(updateUI);
To support adding recipes, we will add a simple input and button and use our stores dispatch()
method together with the addRecipe() action creator to send actions to the store:
Logging
Now that our UI allows us to add new recipes, we find that its hard to see what actions are sent to
the store. One option is to log received actions from the root reducerbut as we will see shortly, this
can be problematic. Another option is to use the middleware we discussed in the previous chapter.
The store holds a connection to all the middleware and they get actions before the reducers, which
means they have access to any actions dispatched to the store. To test this, lets create a simple
logging middleware that will print any action sent to the store:
The structure might seem strange at first, as we are creating a function that returns a function that
returns a function. While this might be a little confusing, it is required by the way Redux combines
middlewares in its core. In practice, in the inner most function we have access to the dispatch()
and getState() methods from the store, the current action being processed, and the next() method,
which allows us to call the next middleware in line.
Our logger prints the current action and then calls next(action) to pass the action on to the
next middleware. In some cases, middleware might suppress actions or change them. That is why
implementing a logger in a reducer is not a viable solution: some of the actions might not reach it.
To connect the middleware to our store, we need to modify our store/store.js file to use Reduxs
applyMiddleware() utility function:
Chapter 2. Your First Redux Application 37
API middleware
11 } else {
12 response.json().then(callback);
13 }
14 })
15 .catch((err) => console.log(`Error fetching recipes: ${ err }`))
16 }
17
18 const apiMiddleware = ({ dispatch }) => next => action => {
19 if (action.type === FETCH_RECIPES) {
20 fetchData(URL, data => dispatch(setRecipes(data)));
21 }
22
23 next(action);
24 };
25
26 export default apiMiddleware;
The main code of our middleware is a simple if statement that calls the fetchData() function and
passes it a callback that dispatches setRecipes() with the returned data:
We also need to modify our reducers/recipes.js to support the new 'SET_RECIPES' action:
The code for the reducer is surprisingly simple. Since we get a new list of recipes from the server,
we can just return that list as the new recipes list:
1 case SET_RECIPES:
2 return action.data.recipes;
Finally, we can remove the initialState we passed to our store, since we will be getting data from
the server. Each of the reducers has default values for its subtrees (remember the recipes = [] from
above?), and the reducers will be the one to construct the initial state automatically. This magic is
explained in the Reducers Chapter.
Heres our new store/store.js:
Chapter 2. Your First Redux Application 40
store/store.js
In a real application the API middleware will be more generic and robust. We will go into
much more detail in the Middleware Chapter.
Summary
In this chapter we built a simple Redux application that supports multiple reducers, middleware,
and action creators. We set up access to a server and built a minimal UI using jQuery. In the books
Git repository, you can find the full source code for this example, including the missing parts (like
the ingredients UI).
https://github.com/redux-book
Part 2. Real World Usage
Chapter 3. State Management
One of the main strengths of Redux is the separation of state (data) management from the
presentation and logic layers. Due to its division of responsibilities, the design of the state layout
can be done separately from the design of the UI and any complex logic flows.
1 const state = {
2 books: [
3 {
4 id: 21,
5 name: 'Breakfast',
6 recipes: [
7 {
8 id: 63,
9 name: 'Omelette',
10 favorite: true,
11 preparation: 'How to prepare...',
12 ingredients: [...]
13 },
14 {...},
15 {...}
16 ]
17 },
18 {...},
19 {...}
20 ]
21 };
While this state layout contains all the required information and conforms exactly to the description
of our application, it has a couple of issues:
Chapter 3. State Management 43
Action-aware reducers
In this implementation, all the parent reducers must be aware of any actions used in their children.
Any changes or additions will require us to check multiple reducers for code changes, thus breaking
the encapsulation benefits of multireducer composition and greatly complicating our code.
The second option is for reducers to pass all actions to their children:
Action-passing reducer
In this implementation, we separate the reducer logic into two parts: one to allow any child reducers
to run and the second to handle the actions for the reducer itself.
While this implementation doesnt require the parent to know about the actions supported by its
children, we are forced to run a very large number of reducers for each recipe. A single call to an
action unrelated to recipes, like UPDATE_PROFILE, will run recipesReducer() for each recipe, and
have it in turn run ingredientsReducer() for each of the ingredients.
Also, since this code (or similar) will be used for the UI, any changes to the structure of the state will
need to be reflected not just in the reducers but in the UI as well. This approach breaks our separation
of concerns model and might require extensive changes to the UI layer(s) on state structure changes.
State as a Database
A recommended approach to solve the various issues raised above is to treat the application state as
a database of entities. In our example, we will break down the nesting to make our state as shallow
as possible and express connections using IDs:
Normalized state
1 const state = {
2 books: {
3 21: {
4 id: 21,
5 name: 'Breakfast',
6 recipes: [63, 78, 221]
7 }
8 },
9
10 recipes: {
11 63: {
12 id: 63,
13 book: 21,
14 name: 'Omelette',
15 favorite: true,
16 preparation: 'How to prepare...',
17 ingredients: [152, 121]
18 },
19 78: {},
Chapter 3. State Management 46
20 221: {}
21 },
22
23 ingredients: {}
24 };
In this structure, each object has its own key right in the root of our state. Any connections between
objects (e.g., ingredients used in a recipe) can be expressed using a regular ordered array of IDs.
There are two things to note in this implementation compared to what we saw with the denormalized
state:
The books reducer is not even mentioned. Nesting levels only affect the parent and children, never
the grandparents. The recipes reducer only adds an ID to the array of ingredients, not the whole
ingredient object.
To take this example further, the implementation of UPDATE_RECIPE would not even require any
change to the recipes reducer, as it can be wholly handled by the ingredients reducer.
The main improvement is that we do not need to be aware of the structure or nesting of the state to
access deeply nested information. Rather, we treat our state as a conventional database from which
to extract information for the UI.
1 {
2 id: 63,
3 name: 'Omelette',
4 favorite: true,
5 preparation: 'How to prepare...',
6 ingredients: [
7 {
8 id: 5123,
9 name: 'Egg',
10 quantity: 2
11 },
12 {
13 id: 729,
14 name: 'Milk',
15 quantity: '2 cups'
16 }
17 ]
18 };
Since the only way to update the Redux store is by sending actions to the reducers, we must build a
payload that can be easily handled by our reducers and find a way to extract the payload from the
denormalized server-returned data.
1 const updateData = ({
2 type: UPDATE_DATA,
3 payload: {
4 recipes: {
5 63: {
6 id: 63,
7 name: 'Omelette',
8 favorite: true,
9 preparation: 'How to prepare...',
10 ingredients: [5123, 729]
11 }
Chapter 3. State Management 49
12 },
13 ingredients: {
14 5123: {
15 id: 5123,
16 name: 'Egg',
17 quantity: 2
18 },
19 729: {
20 id: 729,
21 name: 'Milk',
22 quantity: '2 cups'
23 }
24 }
25 }
26 });
Using this approach, our recipes reducers support for UPDATE_DATA can be as simple as:
Our reducer checks if the payload contains any recipes and merges the new data with the old
recipes object (thus adding to or otherwise modifying it as needed).
A simple approach might be to have a custom function that knows each APIs return data and
normalizes the returned nested JSON into a flat structure with custom code.
Since this is quite a common practice, the custom code can be replaced by the normalizr library.
Using this library, we can define the schema of the data coming from the server and have the
normalizr code turn our nested JSON into a normalized structure we can pass directly into our
new UPDATE_DATA action.
Persisting State
In many cases, we will want to keep the current state even across a page refresh or the applications
tab being closed. The simplest approach to persisting the state is keeping it in the browsers local
storage.
To easily sync our store with local storage (or any other storage engine), we can use the redux-
persist library. This will automatically serialize and save our state once it has been modified.
To use the library, simply install it with npm and modify the store creation file to wrap createStore
with an enhancer:
Setting up redux-persist
Once persistStore(store) has been called, our store and the browsers local storage will automat-
ically be in sync and our store will persist across page refresh.
Real-World State
In a real-world application, our state will usually contain a number of different entities, including the
application data itself (preferably normalized) and auxiliary data (e.g., current access token, pending
notifications, etc.).
Sample state
1 const state = {
2 books: { },
3 recipes: { },
4 ingredients: { },
5 ui: {
6 activeRequests: 0
7 },
8 currentUser: {
9 name: 'Kipi',
10 accessToken: 'topsecrettoken'
11 }
12 };
As our application grows, more types of state entities will creep in. Some of these will come from
external libraries like redux-forms, react-redux-router, and others that require their own place in
our state. Other entities will come from the applications business needs.
For example, if we need to support editing of the users profile with the option to cancel, our
implementation might create a new temp key where we will store a copy of the profile while it
is being edited. Once the user clicks confirm or cancel, the temp copy will either be copied over
to become the new profile or simply deleted.
1 const state = {
2 db: {
3 books: { },
4 recipes: { },
5 ingredients: { },
6 },
7 local: {
8 ui: {
9 activeRequests: 0
10 },
11 user: {
12 name: 'Kipi',
13 accessToken: 'topsecrettoken'
14 }
15 },
16 vendor: {
17 forms: {},
18 router: {}
19 }
20 };
This allows for easier management of the different parts when deciding what needs to be synced to
local storage, or when clearing stale data.
In general, the state is the frontends database and should be treated as such. It is important to
periodically check the current layout and do any refactoring to make sure the states structure is
clean, clear, and easy to extend.
If the answer to any of these questions is yes, the data should go into the state. If the answer to all
of these questions is no, it could still go into the state, but its not a must.
A few examples of data that can be kept outside of the state:
We can consider this similar to putting data in a database or keeping it temporarily in memory.
Some information can be safely lost without affecting the users experience or corrupting his data.
Summary
In this chapter we discussed the structure of our Redux state and how it should be managed for
easier integration with reducers and the UI. We also learned that the state should be considered the
applications database and be designed separately from the presentation or logic layers.
In the next chapter we will talk about server communication, the best method of sending and
receiving data to and from our server using middleware.
Chapter 4. Server Communication
Server communication is one of the more important parts of any application. And while the
basic implementation can be done in a few lines of code, it can quickly grow into a complicated
mechanism able to handle authentication, caching, error handling, WebSockets, and a myriad of
other functionality and edge cases.
Online tutorials usually devote little time to this topic, and suggest using regular promises and the
redux-thunk middleware to let action creators dispatch() actions when data is fetched. When a
project grows, however, it becomes clear that its best to have a single place to handle authentication
(setting custom headers), error handling, and features like caching. Considering this, we need to
be able to both access the store and dispatch asynchronous eventswhich is the perfect job for a
middleware.
Before reading this chapter, it is strongly recommended that you read the Middleware
Chapter.
Did you notice we handled the error in response.json() instead of the fetch() API?
Chapter 4. Server Communication 55
1. We cant see an action going out in our logs before the fetch() completes, preventing the
regular Redux debug flow.
2. Every action creator will need to have the repetitive functionality to handle errors and set
headers.
3. Testing gets harder as more async action creators are added.
4. If you want to change the server communication strategy (e.g., replace fetch() with
WebSockets), you need to change multiple places in the code.
Keeping the code as short and stateless as possible, in keeping with the spirit of Redux, is easier
when all the action creators are simple functions. This makes them easier to understand, debug, and
test. Keeping the action creators clean from async code means moving it into another part of the
stack. Luckily, we have the perfect candidatemiddleware. As we will see, using this approach we
can keep the action creators simple and generate actions that contain all the information needed for
a middleware to perform the API request.
Here, we are using the Fetch API to access the server. Our target URL is built from a
constant specifying the root (e.g., 'http://google.com/') and the sub-URL we want to
access, passed to us in the action object.
API Middleware
Our goal is to create a generic middleware that can serve any API request and requires only the
information passed to it in an action. The simplest solution is to define a new action type that is
unique to API-related events:
Our middleware will listen to any action with type 'API' and use the information in its payload to
make a request to the server. It will let any other actions flow down the middleware chain.
https://developer.mozilla.org/en/docs/Web/API/Fetch_API
Chapter 4. Server Communication 56
1 fetch(`user/${id}`)
2 .then(response => response.json()
3 .then(userData => dispatch(setUserData(userData))
4 .catch(error => dispatch(apiError(error)));
This code has a few hardcoded issues that we will need to address in our generic API:
Since we plan to pass all this information inside our action object, we can expect the corresponding
action creator to pass in all the required parameters.
The URL-building issue is simple to solve, simply by passing the required URL in the action object
(we will start with GET-only APIs):
Target URL
1 fetch(BASE_URL + action.url)
To make things more generic, actions will only hold the relative part of the servers full URL.
This will allow us to easily set the BASE_URL to be different in production, development,
and testing.
Handling the return value from the call is a bit more complex, as our middleware needs to figure
out what action to dispatch. A simple solution is to pass the next action to be dispatched inside the
API action. In our case we will use the Flux Standard Action (FSA) convention and put the ID of the
next action inside the success key (i.e., action.payload.success):
Combining these two ideas results in the following basic API middleware:
Our middleware should call the server and resume the regular application flow. At a later time, once
the call from the server completes, it should dispatch a new action to the store:
Chapter 4. Server Communication 58
1 {
2 type: SET_RECIPES,
3 payload: [ .. array of recipes from server ..]
4 };
Error Handling
Our current example ignores any error handling. To solve this problem, we need to extend our
middleware to catch server errors and dispatch events when they happen.
Error handling could be done in a number of ways:
Dispatch a custom error message (based on data in the action object) on an error event.
Dispatch a generic API error action to be handled by a special reducer or another middleware.
Combine both approaches with a fallback for #2 if #1 was not provided.
For example, here we are using FSA-compliant action creators to send a generic apiError() message
on any failed server request:
We leave it to you as an exercise to add support for custom error handling for actions (a way for an
action creator to specify it wants a different error flow than the default).
Chapter 4. Server Communication 59
1 dispatch(apiStart());
2
3 fetch(BASE_URL + action.payload.url)
4 .then(response => {
5 dispatch(apiDone());
6 // ...
7 })
8 .catch(error => {
9 dispatch(apiDone());
10 // ...
11 })
12 }
To keep track of pending requests, we can keep a counter in the state under a serverStatus or ui
containers in the state. The counter can be used by the UI to show a spinner if the number of pending
requests is greater than zero:
This method allows us to handle different action types in different reducers. However, this approach
is not flexible, encourages code repetition, and forces us to have multiple action types defined for
every API action.
Another approach is to store the response status in the dispatched action:
This might look simple, as you can reuse one action in the middleware, but it causes reducers to
contain more control flow and logic and it makes logging and debugging more difficult (because we
get the same action types in the log and need to accurately verify which one of them had a particular
status).
A third approach is to create dynamic action types that will follow a single convention:
Chapter 4. Server Communication 61
constants/action-types.js
With this approach we can use the same action type constant to handle three cases for async actions:
reducers/recipes.js
Since our API middleware is already checking for success and pending in the action.payload, the
action creators can simply merge the async-ready costs into the resulting payload:
This action creator will result in the following action being returned:
Chapter 4. Server Communication 62
1 {
2 type: 'API',
3 payload: {
4 url: 'recipes',
5 PEDNING: 'RECIPES_PENDING',
6 SUCCESS: 'RECIPES_SUCCESS',
7 ERROR: 'RECIPES_ERROR'
8 }
9 };
Authentication
A common place to store the current users information (such as the access token) is in the Redux
store. As all our API logic is now located in one place and the middleware have full access to the
store using the getState() method, we can extract the accessToken from the state and set it as a
header for our server requests:
All our server calls will now automatically get the correct headers without us having to worry about
this in any other parts of our application.
More Extensions
There are still quite a few things missing from our API middleware to make it usable in the
real world. We need support for more verbs (not just GET), setting of custom headers, timeouts,
caching, and more. Much of this can be done by taking data from the action object or from
the store itself. A robust API middleware solution is already available as (redux-api-middle-
ware)[https://github.com/agraboso/redux-api-middleware].
Chapter 4. Server Communication 63
Chaining APIs
Sometimes fetching data from a server requires a number of different callsfor instance, when the
returned data from one is needed to issue additional calls. A simple example might be fetching the
current user and then the users profile. If we were to use promises in an action creator, the solution
would appear to be quite straightforward:
Chaining promises
Scary stuff there. There are a few issues with this code:
Error handling Exact branching and catching of errors is not obvious and can become
complex as the chain grows.
Debugging It is hard to debug and understand what stage in the chain we are at.
Cancellation and recovery It is nearly impossible to cancel or abort the chain if the user
navigates to a different part of the UI and the current request chain is no longer needed.
As we can see, chaining promises is not an ideal solution. When working with Redux we have two
other alternatives to use, middleware and sagas.
Unfortunately, this approach has its problems too. This setup will cause the fetchProfile() action
to be dispatched every time someone dispatches SET_CURRENT_USER. There might be a flow where
we dont need the profile fetch but cant prevent the middleware from scheduling it.
We can solve this problem by creating a special action flow that has similar behavior to fetchCur-
rentUser() but also triggers the fetchProfile() acton. This can be done by creating a new action
creator and action:
19 next(action);
20 }
This approach requires changing our action creators in a somewhat unclear way. While it will work,
it might cause bugs if we forget to issue the regular setCurrentUser() call from our special action
handler. On the positive side, it will be much easier to debug as its clear exactly what type of fetch
we are performing.
A cleaner approach would be to allow our async action creators to pass an array of actions that the
API middleware needs to dispatch() when a request completes successfully:
This approach allows us to fetch the current user with or without a profile update:
There are a number of suggestions on flow handling in the Middleware Chapter that might make
this flow even simpler to managefor example, using sagas.
https://github.com/redux-saga/redux-saga
Chapter 4. Server Communication 67
In this example we have an endless loop that waits for the FETCH_CURRENT_USER action to be
dispatched. When this occurs, the code starts waiting for the corresponding SET_CURRENT_USER
action. The payload can be used to dispatch a fetchProfile() action to get the corresponding profile
from the server.
This is a very basic example of saga usage and does not handle error or allows to cancel requests
of flows. For more information on sagas, consult the extensive documentation at the official redux-
saga documentation site.
http://redux-saga.github.io/redux-saga/index.html
Chapter 4. Server Communication 68
In this code we used a fake uuid.generate() function; there are a number of different implemen-
tations of such a function, with a simple one being a global counter.
If at a later stage we want to cancel a particular API request, we will need to dispatch a special
action to our middleware with the ID generated for the original action being canceled:
To handle this in our API middleware, we must either cancel the promise (when using implemen-
tations that support this feature) or let the request finish and ignore the response (simply dispatch
nothing):
13 switch (action.type) {
14 case API:
15 fetch(BASE_URL + action.url)
16 .then(response => response.json())
17 .then(handleResponse)
18 );
19 return;
20
21 case CANCEL_API:
22 canceled[action.id] = true;
23 setTimeout(() => delete canceled[action.id], 5000);
24 }
25
26 return next(action);
27 };
To implement this functionality, we simply added an object with keys corresponding to canceled
requests. The setTimeout() function is used to clear canceled requests after 5 seconds to prevent
the object from filling up needlessly.
With this functionality we can cancel requests at any time and not have to worry about request
completion happening long after the user has navigated away from the original location in the app
or after a later request has completed (for example, two consecutive filter requests for data being
sent and the first one returning after the latter one).
Summary
In this chapter we have learned how to set up a comprehensive mechanism for server commu-
nication. We have used Reduxs concept of middleware to move most of the complicated and
asynchronous logic away from our action creators and created a single place where error handling,
caching, and other aspects of server requests can be concentrated.
In the next chapter we will cover WebSocket based communication and how well it can work with
the Redux architecture.
Chapter 5. WebSockets
WebSockets have brought a robust socket communication method directly into our browsers. What
started as a solution for polling data changes on the server is slowly taking over more and more
responsibilities from traditional REST endpoints. The action-based architecture of Redux makes
working with WebSockets exceptionally easy and natural, as it involves using WebSockets as a pipe
to pass actions to and from the server.
Basic Architecture
WebSockets allow us to open a connection to a server and send or receive messages in a fully
asynchronous way. The native implementation in browsers has only four callback methods that
are required to fully support WebSockets:
While multiple WebSockets might be used, most applications will require a single one or at most a
few connections for different servers based on function (chat server, notifications server, etc.).
To start, we will build a system to communicate with a single WebSocket, which can later be
extended for multi-WebSocket support.
Redux Link
The general Redux architecture is all about sending well-defined messages to the store. This same
scheme can work perfectly for server communication over WebSockets. The same structure of plain
objects with the type property can be sent to the server, and we can receive a similarly structured
response back:
Chapter 5. WebSockets 71
A more robust example might be a chat server, where we can dispatch to the store a message similar
to: { id: 'XXX, type: 'ADD_MESSAGE', msg: 'Hello' }. Our store can handle this immediately
by adding the message to the current messages array and send it as is over a WebSocket to the
server. The server, in turn, can broadcast the message to all other clients. Each will get a perfectly
standard Redux action that can be passed directly to their stores.
This way our frontend can use Redux actions to pass information between browser windows and
machines, using the server as a generic dispatcher. Our server might do some additional work, like
authentication and validation to prevent abuse, but in essence can serve as a message passer.
An ideal WebSocket implementation for Redux would allow us to dispatch() actions and have
them smartly routed to the server when needed, and have any actions coming from the WebSocket
be dispatched directly to the store.
Code Implementation
As with any infrastructure-related code, middleware is the perfect place for our WebSocket
implementation. It will allow us to catch any actions that are required to be sent over the WebSocket
and dispatch() anything coming from the server.
To make the code more readable, we can replace the four different assignments with a single use of
Object.assign() and use code similar to this:
Chapter 5. WebSockets 72
Using Object.assign
1 Object.assign(websocket, {
2 onopen() { },
3 onclose() { },
4 onerror(e) { },
5 onmessage(e) { }
6 });
In our middleware, we want to make sure a WebSocket is created only once. Thus, we cannot put
the setup code inside the action handler:
The code in the innermost block gets called every time an action is dispatched, so this would cause
our setup and WebSocket creation code to be called multiple times. To prevent this, we can do the
initialization outside the action callback block:
Lets get back to the initialization code and consider how to handle each of the four callbacks: onopen,
onclose, onerror, and onmessage.
onopen
This is mainly an informative stage; we need to indicate to ourselves that the socket is ready to send
and receive data and might choose to notify the rest of the Redux application that the socket is ready
(perhaps to show some indication in the UI).
Once the socket is open, we dispatch a simple { type: 'WS_CONNECTED' } action to notify the rest
of Redux:
Chapter 5. WebSockets 73
Handling onopen
The wsConnected() function is a simple action creator that should be implemented in one of the
action creator files:
app/actions/ui.js
onclose
The close or disconnect event is very similar to onopen and can be handled in the exact same way:
Handling onclose
onerror
The WebSocket implementation in a browser can provide information on various failures in the
underlying socket communication. Handling these errors is similar to handling regular REST API
errors, and might involve dispatching an action to update the UI or closing the socket if needed.
In this example we will stop at a generic console.log() and leave it to the reader to consider more
advanced error handling methods:
Handling onclose
onmessage
This callback is called every time a new message is received over a WebSocket. If we have built our
server to be fully compatible with Redux actions, the message can simply be dispatched to the store:
Chapter 5. WebSockets 74
Handling onmessage
Before sending any actions, we need to make sure that the WebSocket is open and ready for
transmissions. WebSockets have a readyState property that returns the current socket status.
1 const SOCKET_STATES = {
2 CONNECTING: 0,
3 OPEN: 1,
4 CLOSING: 2,
5 CLOSED: 3
6 };
7
8 if (websocket.readyState === SOCKET_STATES.OPEN) {
9 // Send
10 }
Even when the socket is open, not all actions need to be sent (for example, the TAB_SELECTED or
REST_API_COMPLETE actions), it is best to leave the decision of what to send to our action creators.
The standard way to provide special information about actions to middleware is to use the meta key
inside an action. Thus, instead of using a regular action creator:
Chapter 5. WebSockets 75
This way our middleware can use the meta.websocket field to decide whether to pass the action on
or not:
Note, however, that this code might cause a surprising bug. Since we are sending the whole action
to the server, it might in turn broadcast it to all other clients (even ourselves). And because we didnt
remove the actions meta information, the other clients WebSocket middleware might rebroadcast
it again and again.
A Redux-aware server should consider stripping all meta information for any action it receives. In
our implementation we will remove this on the client side, though the server should still do the
check:
Chapter 5. WebSockets 76
Using this approach, sending actions to our server via a WebSocket becomes as simple as setting the
meta.websocket field to true.
middleware/ws.js
20
21 onclose() {
22 active = false;
23 dispatch(wsDisconnected())
24 },
25
26 onerror(error) {
27 console.log(`WS Error: ${ error.data }`);
28 },
29
30 onmessage(event) {
31 dispatch(JSON.parse(event.data));
32 }
33 });
34
35 return action => {
36 if (websocket.readyState === SOCKET_STATES.OPEN &&
37 action.meta &&
38 action.meta.websocket) {
39
40 // Remove action metadata before sending
41 const cleanAction = Object.assign({}, action, {
42 meta: undefined
43 });
44 websocket.send(JSON.stringify(cleanAction));
45 }
46
47 next(action);
48 };
49
50 export default wsMiddleware;
Authentication
Handling authentication with WebSockets might be a little tricky as in many applications, WebSock-
ets are used alongside regular HTTP requests. The authentication will usually be done via regular
REST or OATH calls and the frontend granted a token - either set in cookies or to be saved in
LocalStorage.
To allow the server to authenticate a WebSocket, a special - agreed upon action - needs to be sent
by the client. In the case of Redux, a special action object can be serialized and sent before doing
any other work over WebSockets.
Chapter 5. WebSockets 78
Sample Flow
A simple way to implement authentication might be to send an API action to our server containing
an email and a password:
1 dispatch({
2 type: API,
3 payload: {
4 url: 'login',
5 method: 'POST',
6 success: LOGIN_SUCCEESS,
7 data: {
8 email: 'info@redux-book.com',
9 password: 'top secret'
10 }
11 }
12 });
If successful, our API Middleware will dispatch the LOGIN_SUCCESS action containing the informa-
tion returned from the server:
1 {
2 type: LOGIN_SUCCEESS,
3 payload: {
4 token: 'xxxYYYzzzz'
5 }
6 }
Our users reducer will probably act on this action to add the token to the state - to be passed in
headers of future API requests to the server.
To make WebSockets authenticate using this token, we can add special code to our WebSocket API
that will check for LOGIN_SUCCESS (and LOGOUT_SUCCESS)
Chapter 5. WebSockets 79
Now the passage of LOGIN_SUCCESS will cause a new WebSocket enabled action to be dispatched
and processed by our middleware to authenticate with the server.
1 > Store:
2 { type: API, payload: ... }
3
4 > Server:
5 POST http://.../login
6
7 > Store:
8 { type: LOGIN_SUCCESS, payload: token }
9
10 > Store:
11 { type: WEBSOCKET_AUTH, payload: token, meta: { websocket: true }}
12
13 > WebSocket:
14 { type: WEBSOCKET_AUTH, payload: token }
Notes
For a full flow of the WebSocket middleware, it would be best to keep track of the authentication
state of the WebSocket and prevent actions from being sent or received before the WebSocket has
been authentication or after it has been logged out from.
Chapter 5. WebSockets 80
When the token is already present in cookie it will be passed to WebSocket as soon as the socket
is openned. This might cause problems if the login process happens after the application loads. Or
even worse, when the user logs out our WebSocket might still stay authenticated. It is better to use
the action based authentication approach described above to avoid these and similar issues.
Summary
This chapter has illustrated how well WebSockets work with Redux and the practical steps needed
to set up WebSocket-based communication.
In the next chapter we will cover the subject of testing and how each part of our Redux application
can be tested separately and together.
Chapter 6. Tests
One of the key strengths of Redux is its ease of testability. To fully automate the testing suite, we
can create unit tests for each of the different actors (reducers, action creators, and middleware) and
combine them together for comprehensive integration tests.
There are a large number of testing tools available, but the exact tooling is less important as
most parts of our Redux application will rely on plain JavaScript functions and objects with no
complicated libraries or async flows to test.
As our testing framework we will be using the excellent Jest library from Facebook, the latest
version of which proves to be an excellent choice for testing Redux. Using other frameworks and
tools such as Karma, Mocha, and so on should look very similar to the examples in this chapter.
To find the best way to add Jest to your project and operating system, please follow
Jests getting started guide.
https://facebook.github.io/jest/
https://facebook.github.io/jest/#getting-started
Chapter 6. Tests 82
1 describe('actions', () => {
2 // TODO: Add tests
3 });
Inside this function other nested describe() functions can be used, to further distinguish between
different sets of states (for example, testing failing or succeeding API calls).
Each test in Jest is wrapped within an it() block describing what the test does. To keep the tests
readable and easy to understand, it is generally recommended to create as many short tests as
possible (each within its own it() block) rather than creating very large single test functions:
1 describe('actions', () => {
2 it('should create an action to add a todo', () => {
3 // TODO: Implement test
4 });
5
6 it('should create an action to delete a todo', () => {
7 // TODO: Implement test
8 });
9 });
Our setRecipes() action creator receives a single parameter and creates a plain JavaScript object
in return. Since there is no control flow logic or side effects, any call to this function will always
return the same value, making it very easy to test:
Chapter 6. Tests 83
This test is built in three parts. First, we calculate what our action creator should return when called
with 'test' as an argumentin this case a JavaScript object containing two keys, type and payload:
The second stage is running the action creator actions.addRecipe('test') to get the value built
by our action creators implementation:
And the final stage is using Jests expect() and toEqual() functions to verify that the actual and
expected results are the same:
1 expect(actual).toEqual(expected);
If the expected and actual objects differ, Jest will throw an error and provide information describing
the differences, allowing us to catch incorrect implementations.
Using Snapshots
The approach of calculating the expected value and then comparing it to dynamically calculated
values is very common in Redux tests. To save typing time and make the code cleaner to read, we
can use one of Jests greatest features, snapshots.
Instead of building the expected result, we can ask Jest to run the expect() block and save the result
in a special .snap file, generating our expected object automatically and managing it for us:
The expected calculation is gone, and instead of using isEqual(), Jest will now compare the result
of the expression inside expect() to a version it has saved on disk. The actual snapshot is placed in
a __snapshots__ directory in a file with the same name as the test file plus the .snap extension:
snapshots/action.test.js.snap
https://facebook.github.io/jest/docs/tutorial-react.html#snapshot-testing
Chapter 6. Tests 85
The structure is more complicated than that of a regular JavaScript object, but the result is exactly
the same as our original expected calculation:
What happens when our code changes? In some cases we want to intentionally change
the structure of our action object. In these cases, Jest will detect that the returned value
does not match what is saved inside its snapshot file and throw an error. But if we
determine that the new result is the correct one and the cached snapshot is no longer
valid, we can easily tell Jest to update its snapshot version to the new one.
The modified addRecipe() action creator will set payload to "Default" if the user does not provide
a title. To test this behavior we can create two tests, one that provides a parameter (as we already did)
and one that provides an empty string. A fully comprehensive test might contain multiple empty
string cases, for null, undefined, and '':
In contrast to what we discussed earlier, here we tried putting multiple expect() functions into the
same test. While this approach will work, it will be harder to identify which of the test cases failed
in the event of an error.
Since we are using JavaScript to write our tests, we can easily create test cases for each input value
without increasing our code size significantly (by creating an it() clause for each). We can do that
by adding all the possible inputs into an array and automatically creating corresponding it() blocks:
Chapter 6. Tests 86
Using this approach we get three different it() blocks automatically generated by JavaScript,
keeping our tests clear and the code short.
With redux-thunk, our action creators can return a function instead of a plain JavaScript object.
The thunk middleware will call such a function and pass it the stores dispatch() and getState()
methods. This allows the action creator to use the async fetch() API to get data from the server
and dispatch an action when its done using dispatch().
https://github.com/gaearon/redux-thunk
Chapter 6. Tests 87
Stubbing fetch()
Before we move on to the actual tests, we need to lay out some infrastructure for stubbing the
fetch() API. We are going to create a mock response object and then stub the fetch() API on our
window object:
The mock fetch() call will return a resolved promise that is similar to the real result from a fetch()
call:
This code will allow us to call mockFetch() or mockFetchError(), causing the next call to fetch()
to return with our mocked response. The only issue with this implementation is that it stubs all the
fetch() calls in the system, regardless of the URL.
Since the first parameter to fetch() is the URL, we can use the common handleResponse() function
to first verify that the URL passed to fetch() is the URL we have stubbed:
Chapter 6. Tests 88
Here we create a mock store object with a single thunk middleware. This store object can be used
as a regular Redux store; it supports dispatching of actions and will later allow us to assert that the
correct set of actions was sent to our store.
Our mock store gets automatically re-created before each iteration of the tests, clearing any actions
cached from the previous run.
Async test
1 it('should fetch recipe if it exists', () => {
2 return store.dispatch(actions.fetchRecipe(100));
3 });
Since store.dispatch() in this case returns a promise (remember our fetchRecipe() action creator
returns a call to the fetch() API), we can use it to create an async test.
To add an expect() clause to the code, we can use the same promise and run our tests as soon as it
is resolved:
The expect() clause is similar to what we used in our previous tests. We are using the mocked stores
getActions() method to get an array of all the actions dispatched to the store. In our implementation
we expect a successful call to fetch() to dispatch the result of the setRecipe() action creator.
Running this test now will fail, since we didnt mock the fetch() API. Using the small utility library
we created previously, we can create the mock that will result in the correct action sequence:
Chapter 6. Tests 90
Here we mock a 200 successful response from the fetch() API and expect that dispatching the async
action created by fetchRecipe(100) results in a later dispatch of the action created by setRecipe().
Reducer Tests
Testing reducers is very similar to testing action creators, as reducers by definition are idempotent
(given a state and an action, the same new state will be returned every time).
This makes reducer tests very easy to write, as we simply need to call the reducer with different
combinations of input to verify the correctness of the output.
There are two main test cases to consider, adding a recipe to an empty list and a non-empty one. We
can test the first case as follows:
Before we simplify the code, lets consider the second test case, adding a recipe to a non-empty list:
In this test we start with a list containing a single item and update our expected result to match.
While this works, it has a maintenance problem. What will happen if our recipes contain more fields
in the future?
Using this method of writing tests, we will need to find each test definitions initial state and add
more properties to it. This complicates the test writers job without providing any benefits. Luckily,
we already have a way to create non-empty states: the reducer! Since we already tested adding to an
empty list in the first test, we can rely on our reducer to create a non-empty list with all the required
recipe information:
This only partially solves the problem, though, as we are still treating the initial state as an empty
array ([]). While this is true in our test case, other reducers might have more complicated structures
to deal with. A simple solution would be to create a const initialState = {} at the root of the
tests and rely on it when needed:
Chapter 6. Tests 93
The same initialState is used in all the tests, but it is still hardcoded in our test file. If our reducer
changes the way state is built, we will be forced to update the test files accordingly. To remove this
dependency we can rely on a feature that is forced by Reduxs combineReducers(). It mandates that
any reducer called with an undefined state must return its part of the initial state structure:
This means we can use the reducer to get the initial state to use for all of our tests, simply by calling
it with undefined and any action:
Chapter 6. Tests 94
The result will put the same [] in the initial state, but now any changes to what the reducer considers
to be the initial state will be automatically picked up by the tests as well.
Original tests
The first step will be to combine action, actual, and expect() into a single line:
Chapter 6. Tests 95
Simplified tests
The second step is to use Jests snapshots instead of manually calculated expected values:
Avoiding Mutations
One key requirement is that our reducers never modify the state, but only create a new one.
Our current tests do not catch these issues (try changing .concat() to .push() in the reducer
implementation).
Chapter 6. Tests 96
While we can try to catch these mistakes by manually verifying that the initial state did not change,
a simpler approach would be to freeze the initial state and have any changes to it automatically
stop the tests. To achive this we can use the excellent deep-freeze library, installed as follows:
Installing deep-freeze
Using deep-freeze
Any attempt by any parts of our code to modify initialState will now automatically throw an
error:
1 initialState.push('test');
2 > TypeError: Can't add property 0, object is not extensible
To ensure that our reducers never change the original state, we can always call deepFreeze() on
the state passed as the first parameter to a reducer:
https://github.com/substack/deep-freeze
Chapter 6. Tests 97
While somewhat breaking the unit test principle, combining the reducers with action creators results
in cleaner code, fewer bugs, and less duplication.
Unknown Actions
One last issue to test with reducers is that they gracefully handle unknown actions and return the
original state passed to them without modifications.
Since every action can propagate to the whole reducer tree, it is important for the
reducer to return the original state and not a modified copy. This will allow UI libraries
to identify changes in the tree using reference comparison.
An important thing to note about this test is the use of .toBe() instead of .toEqual() or
.toMatchSnapshot(). Unlike the other methods, .toBe() expects the result of the reducer to be
the exact same object, not a similar object with the same data:
The main goal of this test is to verify that our reducer returns the original state if the action sent
was not intended for it:
Testing Middleware
Middleware are where most of the complex logic of our application will reside. Since they have full
access to the stores dispatch() and getState() methods as well as control over the actions flow
via next(), middleware can become quite complex with nontrivial asynchronous flows.
Chapter 6. Tests 99
Middleware signature
To test middleware we will need to mock dispatch(), getState(), and mock() and call sampleMid-
dleware() and nextWrapper() to get our test target, the innerCode() function:
We can now use the regular Jest tests to test the middleware() function we built by calling it with
action objects:
In the case of our simple middleware, we only want to verify that it passed the action correctly down
the chain by calling next(action). Since we used Jests function mocking, we can get a full history
of calls to each mock by accessing next.mock.calls:
1 expect(next.mock.calls.length).toBe(1);
2 expect(next.mock.calls[0].length).toBe(1);
3 expect(next.mock.calls[0][0]).toEqual(sampleAction);
Our test verified that there was only one call to next(). In that call there was only one parameter
passed, and that parameter was the sample action.
We could do all the three tests in one go by using:
1 expect(next.mock.calls).toEqual([[sampleAction]]);
Generic setup
15
16 expect(next.mock.calls).toEqual([[sampleAction]]);
17 };
18 };
Using this structure, our middleware will be rebuilt before each test and all the mocked functions
will be reset, keeping the testing code itself as short as possible.
API middleware
1 import 'whatwg-fetch';
2 import { API } from 'consts';
3 import { apiStarted, apiFinished, apiError } from 'actions/ui';
4
5 const apiMiddleware = ({ dispatch }) => next => action => {
6 if (action.type !== API) {
7 return next(action);
8 }
9
10 const { url, success } = action.payload;
11
12 dispatch(apiStarted());
13
14 return fetch(url)
15 .then(response => response.json())
16 .then(response => {
17 dispatch(apiFinished());
18 dispatch(success(response));
19 })
20 .catch(({ status, statusText }) =>
21 dispatch(apiError(new Error({ status, statusText }))))
22 };
23
24 export default apiMiddleware;
Our middleware catches any actions of the type API, which must contain a payload key with a url
to make a request to and a success parameter that holds an action creator to call with the returned
data:
Chapter 6. Tests 102
In our Redux call, calling dispatch(apiAction()) will result in our API middleware doing a GET
request for server/fake.json and (if successful) dispatching the SET_DATA action with payload
set to the response. When there is an error, an action created by apiError() will be dispatched
containing status and statusText.
Another important feature of the API middleware is that it will dispatch apiStarted() before
contacting the server and apiFinished() on success (or apiError() on failure). This allows the
application to keep track of the number of active requests to the server and display a spinner or
some other user indication.
To fully test this middleware we can split the tests into three groups: general tests, success tests, and
failure tests.
Setup
To make our tests cleaner we will be using the structure discussed previously and mocking the
fetch() API as discussed in the Async Action Creators section of this chapter.
We will also use the sample API action creators from earlier to drive the tests and a fake data response
from the server:
Chapter 6. Tests 103
General tests
The first test for any middleware is to ensure that it passes unknown actions down the chain. If we
forget to use next(action), no actions will reach the reducers:
Here we verify that dispatch() is never called and next() is called exactly once with our
sampleAction. Since we will be using dispatch.mock.calls and next.mock.calls very often in
our tests, we can shorten them a little by adding the following to our setup code:
Our expect() call only checks that the first dispatch() action is API_STARTED because the
middleware might call additional actions later on.
In the success scenario, we need to mock our fetch() API to return a successful response. We will
be using the same mockFetch() utility created in the Async Action Creators section of this chapter.
Our basic success tests need to check that API_FINISHED is dispatched once the API is done and that
our success() action creator is called, passed the response, and dispatched to the store:
1 describe('success', () => {
2 beforeEach(() => mockFetch('recipes.json', 200, JSON.stringify(data)));
3
4 it('should dispatch API_FINISHED');
5
6 it('should dispatch SET_DATA');
7 });
A first attempt at testing the first case might look similar to the API_STARTED test:
Unfortunately, this code will not work. Since API_FINISHED is only dispatched after the fetch()
promise is resolved, we need to wait for that to happen before calling expect().
As discussed in the Async Action Creators section of this chapter, we rely on our call to the
middleware to return a promise that gets resolved once the fetch() call completes. Only then can
we run assertions and verify that everything behaved according to our expectations:
Chapter 6. Tests 106
In this version of the test, only once the promise returned by the call to middleware() is resolved do
we check the array of calls to dispatch(). Since our new test is a one-liner, we can use some ES2016
magic and Jests toMatchSnapshot() method to shorten the code:
Testing that the API middleware correctly sends the response from the server via the action creator
provided in action.payload.success is very similar:
After the fetch() method is done, we check that the third call to dispatch() sent us the same action
object as a direct call to the setData(data) action creator.
Remember that we mocked the server response for fetch() with mockFetch(), passing it
the stringified version of data.
The failing case is similar to the success one, except that we mock fetch() to fail. There are two tests
in this scenario, verifying that API_FINISHED was not dispatched and that API_ERROR was dispatched
instead:
Chapter 6. Tests 107
1 describe('error', () => {
2 beforeEach(() => mockFetchError('recipes.json', 404, 'Not found'));
3
4 it('should NOT dispatch API_FINISHED', () =>
5 middleware(apiAction()).then(() =>
6 expect(dispatchCalls[1][0].type).not.toBe(API_FINISHED)));
7
8 it('should dispatch error', () =>
9 middleware(apiAction()).then(() =>
10 expect(dispatchCalls[1]).toMatchSnapshot()));
11 });
Here we have used all the methods discussed previously to test both cases.
Integration Tests
The role of the integration tests is to verify that all the parts of the application work correctly
together. A comprehensive unit test suite will ensure all the reducers, action creators, middleware,
and libraries are correct. With integration tests, we will try to run them together in a single test to
check system-wide behavior.
As an example of an integration test, we will verify that when the fetchRecipes() action creator
is dispatched, data is correctly fetched from the server and the state is updated. In this flow we will
check that the API middleware is correctly set up, all the required action creators are correct, and
the recipes reducer updates the state as needed.
Chapter 6. Tests 108
Basic Setup
Since the integration tests will be using the real store, we can simple require and initialize it as in
our regular application:
To make sure our reducer updates the state, we first verify that our initial recipes list is empty
and check that it was changed to contain the server-returned data after the fetchRecipes() action
completed.
Summary
In this chapter we have discussed in detail various methods of testing Redux using the Jest library.
Given the clear division of responsibilities in Redux and in keeping with its plain JavaScript objects
and idempotent functions, most unit tests (and integration tests) are short and simple to write.
This is in turn means that we, as developers, can minimize the time we spend writing tests and still
have a comprehensive and understandable testing suite.
This is the last chapter in the Real World part of this book. In the next part, Advanced Redux,
we will delve deeper into each of Reduxs actors and learn advanced methods for organizing and
managing our code.
Part 3. Advanced Concepts
Chapter 7. The Store
In contrast to most other Flux implementations, in Redux there is a single store that holds all
of the application state in one object. The store is also responsible for changing the state when
something happens in our application. In fact, we could say that Redux is the store. When we talk
about accessing the state, dispatching an action, or listening to state changes, it is the store that is
responsible for all of it.
Sometimes the concern is raised that storing the whole state in one huge JavaScript object
might be wasteful. But since objects are reference-type values and the state is just an object
holding a reference to other objects, it doesnt have any memory implications; it is the same
as storing many objects in different variables.
Creating a Store
To create the store we use the createStore() factory function exported by Redux. It accepts three
arguments: a mandatory reducer function, an optional initial state, and an optional store enhancer.
We will cover store enhancers later in this chapter and start by creating a basic store with a dummy
reducer that ignores actions and simply returns the state as it is:
Sample store
1 import { createStore } from 'redux';
2
3 const initialState = {
4 recipes: [],
5 ingredients: []
6 }
7 const reducer = (state, action) => state;
8
9 const store = createStore(reducer, initialState);
The initialState parameter is optional, and usually we delegate the task of building an
initial state to reducers, as described in the Reducers Chapter. However, it is still useful
when you want to load the initial state from the server to speed up page load times. State
management is covered extensively in the State Management Chapter .
The simple store that we have just created has five methods that allow us to access, change, and
observe the state. Lets examine each of them.
Chapter 7. The Store 112
1 store.getState();
2 // => { recipes: [], ingredients: [] }
Now we can rewrite our reducer to make it able to create an updated state for actions of type
'ADD_RECIPE' and return the current state otherwise:
Listening to Updates
Now that we know how the store updates the state, we need a way to update the UI or other parts
of our application when the state changes. The store allows us to subscribe to state changes using
the subscribe() method. It accepts a callback function, which will be executed after every action
has been dispatched:
Chapter 7. The Store 113
The subscribed callback does not get passed any arguments, and we need to access the
state. So, we must call store.getState() ourselves.
The return value of the subscribe() method is a function that can be used to unsubscribe from the
store. It is important to remember to call unsubscribe() for all subscriptions to prevent memory
leaks:
If your application is too large to bundle into a single file or if you want to gain extra
performance, you usually use a technique called code splittingseparating the production
bundle into multiple files and loading them on demand when the user performs some
interaction or takes a specific route. Implementing lazy loading is outside the scope of this
book, but you might want to know that code splitting also can be applied to the Redux
store, thanks to the replaceReducer() method.
Lets look at a simple example with some functionality available only for authenticated users. At
initial load, our store will only handle the currentUser substatejust enough for the authentication:
Chapter 7. The Store 114
If combineReducers() looks unfamiliar to you, take a look at Chapter 9 to learn about this technique.
For now, lets just assume the functions inside are going to handle a currentUser substate. After the
user signs in, we load the new functionality. Now we need to make our store aware of the new subset
of our application state and the function that should handle it. Here is where the replaceReducer()
method comes in handy:
Keep in mind that when you call replaceReducer(), Redux automatically calls the same initial
action it calls when you first create the store, so your new reducer automatically gets executed and
the new state is immediately available via the store.getState() method. For more on the initial
action, see Chapter 9.
The same technique can be used by development tools to implement the hot reloading mechanism
for a better developer experience. Hot reloading is a concept where source code changes dont cause
a full page reload, but rather the affected code is swapped in place by special software and the
application as a whole is kept in the same state that it was in before the code change. Hot reloading
tools are outside the scope of this book, but you can easily find more information online.
Store as Observable
Starting from version 3.5.0, Redux store can also act as an Observable. This allows libraries like
RxJS to subscribe to the stores state changes. This subscription method is different from the regular
subscribe() method of the store: when subscribing to the store as an observable, the latest state is
passed without the need to call store.getState().
To support older browsers, Redux uses the symbol-observable polyfill when Sym-
bol.observable is not natively supported.
https://github.com/blesh/symbol-observable
Chapter 7. The Store 115
This basic API is interoperable with most common reactive libraries (e.g. RxJS). Any library that
exports the next() method can be subscribed and receive updates. This implementation also
conforms to the tc39/proposal-observable.
If you dont use reactive libraries, you can still subscribe to the store by accessing the Sym-
bol.observable property (or using symbol-observable polyfill like Redux does):
Subscribing with a generic observer will cause the observers next() be called on every state change
and be passed the current store state.
Subscribing to changes
1 const observer = {
2 next(state) {
3 console.log("State change", state);
4 }
5 };
6
7 const observable = store.$$observable();
8
9 const unsubscribe = observable.subscribe(observer);
To unsubscribe, we simply call the function returned from the call to subscribe()
https://github.com/tc39/proposal-observable
Chapter 7. The Store 116
Higher-Order Functions
Before we proceed, lets do a quick overview of what higher-order functions are. We can define
the term as referring to a function that either takes one or more functions as arguments, returns a
function as its result, or does both. Heres an example:
1 function output(message) {
2 console.log(message);
3 }
4
5 function addTimeStamp(fn) {
6 return function(...args) {
7 console.log(`Executed at: ${Date.now()}`);
8 fn(...args);
9 }
10 }
Chapter 7. The Store 117
11
12 const timedOutput = addTimeStamp(output);
13
14 timedOutput('Hello World!');
15
16 > Executed at: 1464900000001
17 > Hello World!
Here, the output() function prints our message to the console. The addTimeStamp() function is
a higher-order function that can take any other function and log the time of execution. Calling
addTimeStamp() with output() as the parameter creates a new wrapped function that has
enhanced functionality. It still has the signature of the original output() function but now also
prints the timestamp.
Multiple decoration
Using multiple decorators is a valid and practical approach, but the resulting code can be hard to
read and appear somewhat cumbersome. Instead, Redux provides the compose() function to handle
multiple wrappers in a cleaner manner:
The simplest implementation of that function is very neat. Notice the use of the reduceRight()
method on the array of functions, which ensures that wrapping of higher-order functions happens
from right to left:
Chapter 7. The Store 118
Implementation of compose
1 function compose(...funcs) {
2 return (...args) => {
3 const last = funcs[funcs.length - 1]
4 const rest = funcs.slice(0, -1)
5
6 return rest.reduceRight(
7 (composed, f) => f(composed),
8 last(...args)
9 )
10 }
11 }
Store Enhancers
Store enhancers are higher-order functions used to enhance the default behavior of the Redux store.
In contrast to middleware and reducers, they have access to all internal store methods (even those
not available to middleware, such as subscribe()).
To give a few examples, there are store enhancers for:
Lets build an example store enhancer that will persist state changes and load initial state from
localStorage. To enhance or decorate a store, we need to write a higher-order function that receives
the original store factory function as a parameter and returns a function similar in signature to
createStore():
We start by creating a store. We first check if an initialState was originally passed. If it was, we
create the store with it. Otherwise, we read the initial state from localStorage:
1 let store;
2
3 if (typeof initialState !== 'function') {
4 store = next(reducer, initialState, enhancer);
5 } else {
6 const preloadedState = initialState ||
7 JSON.parse($localStorage.getItem('@@PersistedState') || {})
8
9 store = next(reducer, preloadedState, enhancer);
10 }
Right after our store is initiated, we subscribe to state updates and save the state to localStorage on
every change. This will ensure our state and the local storage are always in sync. Finally, we return
the decorated store:
Chapter 7. The Store 120
This example doesnt handle errors or edge cases, but it showcases the base of a store enhancer.
To simplify the syntax, Redux allows us to pass the store enhancer as a parameter to createStore():
If youve been following along carefully, you may have noticed that in the sample code at the
beginning of the chapter the second parameter to the createStore() function was initialState.
Both parameters are optional, and Redux is smart enough to distinguish between the state object
and the store enhancer when you pass only two arguments. However, if you also need an initial
state, the parameters of createStore() should come in the following order:
We can use multiple store enhancers to create the final store for our application, and the same
compose() method we saw earlier can be used for store composition as well:
applyMiddleware()
One of the best-known store enhancers is called applyMiddleware(). This is currently the only store
enhancer provided by Redux (if you arent familiar with middleware, head to Chapter 10 for an in-
depth explanation).
Implementation of applyMiddleware
In this example, the word middlewares is used as plural form to distinguish between
singular form in the map function. It is the actual source code of applyMiddleware().
At its core, applyMiddleware() changes the stores default dispatch() method to pass the action
through the chain of middleware provided:
Setup of store
First a store is created and the core getState() and dispatch() methods are wrapped into something
called middlewareAPI. This is the object our middleware receives as the first parameter (commonly
confused with store):
The array of middleware is transformed into the result of calling middleware() with middlewareAPI
as its argument. Since the structure of a middleware is api => next => action => {}, after the
transformation, chain holds an array of functions of type next => action => {}.
The last stage is to use the compose() function to decorate the middleware one after another:
1 dispatch = compose(...chain)(store.dispatch)
This line causes each middleware to decorate the chain of previous ones in a fashion similar to this:
1 middlewareA(middlewareB(middlewareC(store.dispatch)));
The original store.dispatch() is passed as a parameter to the first wrapper in the chain.
This implementation explains the strange syntax of the Redux middleware (the 3-function
structure):
1 const myMiddleware =
2 ({ getState, dispatch }) => (next) => (action) => { }
Other Uses
Store enhancers are powerful tools that allow us to debug stores, rehydrate state on application
load, persist state to localStorage on every action, sync stores across multiple tabs or even network
connections, add debugging features, and more. If youd like an idea of whats available, Mark
Erikson has composed a list of third-party store enhancers in his awesome redux-ecosystem-links
repository.
https://github.com/markerikson/redux-ecosystem-links/blob/master/store.md
https://github.com/markerikson/redux-ecosystem-links
Chapter 7. The Store 123
Summary
In this chapter we learned about the central, most important part of Redux, the store. It holds the
whole state of the application, receives actions, passes them to the reducer function to replace
the state, and notifies us on every change. Basically, you could say that Redux is the store
implementation.
We learned about higher-order functions, a very powerful functional programming concept that
gives us incredible power to enhance code without touching the source. We also covered uses for
store enhancers in Redux, and took a look at the most common store enhancer, applyMiddleware().
This function allows us to intercept and transform dispatched actions before they are propagated to
reducers, and we will take a deeper look at it in Chapter 10.
In the next chapter we will look at actions and action creators, the entities we dispatch to the store
to make changes to the application state.
Chapter 8. Actions and Action
Creators
Actions are the driving force of every dynamic application, as they are the medium by which all
changes are communicated within a Redux application. In a Redux application we have two sides:
the senders of actions and their receivers. The senders might be event handlers (like keypresses),
timeouts, network events, or middleware. The receivers are more limited; in the case of Redux they
are middleware and reducers.
A connection between a sender and a receiver is not necessarily one-to-one. A keypress might cause
a single action to be sent that will in turn cause both a middleware to send a message to the server
and a reducer to change the state, resulting in a pop-up appearing. This also holds true in the other
direction, where a single reducer might be listening to multiple actions. While in very simple Redux
applications there might be a reducer for each action and vice versa, in large applications this relation
breaks down, and we have multiple actions handled by a single reducer and multiple reducers and
middleware listening to a single action.
Since the side emitting the actions doesnt know who might be listening to it, our actions have to
carry all the information needed for the receiving end to be able to understand how to respond.
The simplest way to hold information in JavaScript is to use a plain object, and that is exactly what
an action is:
Actions are plain objects containing one required property, the type. The type is a unique key
describing the action, and it is used by the receiving end to distinguish between actions.
The value of the type property can be anything, though it is considered good practice to
use strings to identify actions. While on first thought numbers or ES2016 symbols might
sound like a better solution, both have practical downsides: using numbers makes it hard
to debug an application and gives little benefit spacewise, whereas ES2016 symbols will
cause issues with server rendering and sending actions across the network to other clients.
In Redux, we send actions to the store, which passes them to middleware and then to reducers. In
order to notify a store about an action, we use the stores dispatch() method.
Unlike many Flux implementations, in Redux the stores dispatch() API is not globally available.
You have a few options to access it:
Chapter 8. Actions and Action Creators 125
Heres a simple example for dispatching actions by holding a direct reference to the store:
To keep our actions consistent across a large code base, it is a good idea to define a clear scheme for
how the action objects should be structured. We will discuss the scheme later in this chapter.
Action Creators
As our applications grow and develop, we will start encountering more and more code like this:
Chapter 8. Actions and Action Creators 126
If we decide to use the same action in other places, we will end up duplicating the logic in multiple
locations. Such code is hard to maintain, as we will have to synchronize all the changes between all
the occurrences of the action.
A better approach is to keep the code in one place. We can create a function that will create the
action object for us:
Any modification to the content or logic of the action can now be handled in one place: the action
creation function, also known as the action creator.
Beyond improving the maintainability of our applications, moving the action object creation to a
function allows us to write simpler tests. We can test the logic separately from the place from which
the function is called.
In a large project, mostif not allof our actions will have a corresponding action creator function.
We will try to never call the dispatch() method by handcrafting the appropriate action object,
but rather use an action creator. This might appear to be a lot of overhead initially, but its value
will become apparent as the project grows. Also, to help reduce boilerplate, there are a number of
libraries and concepts that ease the creation and use of action creators. Those will be discussed later
in this chapter.
Directory Organization
In order to better manage our action creators, we will create a separate directory for them in our
code base. For smaller projects, it may be enough to group actions in files according to their usage
in reducers:
Chapter 8. Actions and Action Creators 127
1 actions/
2 recipes.js // Recipe manipulation actions
3 auth.js // User actions (login, logout, etc.)
4 ...
But as our projects grow in both size and complexity, we will subdivide our action creator directory
structure even more. The common approach is to nest actions based on the data type they modify:
1 actions/
2 recipes/
3 favorites.js // Handle favorite recipe logic
4 ...
5 auth/
6 resetting-password.js // Handle password logic
7 permissions.js // Some actions for permissions
8 ...
9 ...
At first glance it might look easier to put action creators with their corresponding reducers,
sometimes even in the same files. While this approach might work perfectly in the
beginning, it will start breaking down in large projects. As the complexity grows, the
application might have multiple reducers acting on the same action or multiple actions
watched by a single reducer. In these cases the grouping stops working, and the developer is
forced to start decoupling some of the actions or moving them, ending up with the structure
suggested here.
1 const action = {
2 type,
3 error,
4 payload,
5 meta
6 };
1 store.dispatch({
2 type: 'ADD_RECIPE',
3 payload: {
4 title: 'Omelette',
5 description: 'Fast and simple'
6 }
7 });;
If the action were in the error state (for example, in the event of a rejected promise or API failure),
the payload would hold the error itself, be it an Error() object or any other value defining the error:
Chapter 8. Actions and Action Creators 129
1 const action = {
2 type: 'ADD_RECIPE',
3 error: true,
4 payload: new Error('Could not add recipe because...')
5 };
String Constants
In Chapter 2 we briefly discussed the idea of using string constants. To better illustrate the reasoning
behind this approach, lets consider the problems that using strings for type can cause in a large code
base:
1. Spelling mistakesIf we spell the same string incorrectly in the action or the reducer, our
action will fire but result in no changes to the state. Worst of all, this will be a silent failure
without any message to indicate why our action failed to produce its desired effect.
2. DuplicatesAnother developer, in a different part of the code base, might use the same string
for an action. This will result in issues that are very hard to debug, as our action will suddenly
cause that developers reducers to fire as well, creating unexpected changes to the state.
To avoid these issues, we need to ensure a unique naming convention for our actions to allow both
action creators and reducers to use the exact same keys. Since JavaScript doesnt have native enum
structures, we use shared constants to achieve this goal. All the keys used for type are stored in a
single constants file and imported by both action creators and reducers. Using a single file allows
us to rely on JavaScript itself to catch duplication errors. The file will have this form:
constants/action-types.js
In large applications the naming convention will be more complicated to allow developers more
freedom to create constants for different parts of the application. Even in our simple example, being
able to mark both recipes and comments as favorites will require two different MARK_FAVORITE
actions (e.g., RECIPE__MARK_FAVORITE and COMMENT__MARK_FAVORITE):
Chapter 8. Actions and Action Creators 130
constants/action-types.js
1 // Recipes
2 export const ADD_RECIPE = 'ADD_RECIPE';
3 export const DELETE_RECIPE = 'DELETE_RECIPE';
4 export const RECIPE__MARK_FAVORITE = 'RECIPE__MARK_FAVORITE';
5
6 // Comments
7 export const ADD_COMMENT = 'ADD_COMMENT';
8 export const DELETE_COMMENT = 'DELETE_COMMENT';
9 export const COMMENT__MARK_FAVORITE = 'COMMENT__MARK_FAVORITE';
10 ...
constants/action-types.js
actions/places.js
components/recipe.js
15 });
16
17 it ('should set timestamp to now if not given', () => {
18 const description = 'Fast and simple';
19 const expectedAction = {
20 type: ADD_RECIPE,
21 title: 'Untitled',
22 description
23 };
24
25 expect(addRecipe(null, description)).to.equal(expectedAction);
26 });
27 });
More complex action creators might use helper functions to build the action object. A simple example
might be a helper function trimTitle() that removes whitespace from around a string and is used
by a SET_TITLE action. We would test the method separately from the action creator function itself,
only verifying that the action creator called that method and passed it the needed parameters.
redux-thunk
The real power of actions comes with the use of various middleware (discussed more in the
Middleware Chapter). One of the most common and useful ones for learning Redux is redux-thunk.
In contrast to what we learned before, actions passed to dispatch() dont have to be objects, as the
only part of Redux that requires actions to be objects is the reducers. Since the middleware get called
before an action is passed to the reducers, they can take any type of input and convert it into an
object.
This is exactly what redux-thunk does. When it notices that the action is of type function instead
of object, it calls the function, passing it the dispatch() and getState() functions as parameters,
and passes on the return value of this function as the action to perform. That is, it replaces the
function with its return value, which should be the plain JavaScript object the reducers expect.
This approach makes our action creators much more powerful, since they now have access to the
current state via getState() and can submit more actions via dispatch().
Adding redux-thunk to a project takes two steps:
1. Add the middleware to your project by running npm install --save redux-thunk.
2. Load the middleware by adding it to the store using the applyMiddleware() function:
https://github.com/gaearon/redux-thunk
Chapter 8. Actions and Action Creators 133
store/store.js
This is generic code for adding middleware to your project. We will cover this in more
detail in the Middleware Chapter.
With redux-thunk installed, we can start writing action creators that return functions instead of
objects. Going back to our previous example, lets create an action creator that simulates server
communication by using a delay:
Sample action
The main difference from our previous creators is that we are no longer returning an object but
rather a function:
Chapter 8. Actions and Action Creators 134
Sample action
1 function addRecipe(title) {
2 return function(dispatch, getState) {
3 // Action creator's code
4 };
5 }
First, we use a helper function to remove unneeded whitespace from our titles:
Next, we dispatch an action that might show a spinner in our application by setting a fetching flag
somewhere in our state:
Dispatch on load
1 dispatch({ type: ADD_RECIPE_STARTED });
The last step is to send the ADD_RECIPE action, but delayed by one second:
In this example one action creator ended up dispatching two different actions. This gives us a new
tool, allowing us to generate as many granular actions as needed and even to dispatch no actions at
all in certain conditions.
Server Communication
The redux-thunk method can be used to allow simple server communications, simply by replacing
the setTimeout() call from the previous example with an Ajax call to the server:
Chapter 8. Actions and Action Creators 135
This code might be good for a very simple project, but it includes a lot of boilerplate that will be
needed for each API call we make. In the Middleware Chapter we will discuss a more generic and
cleaner approach to server communication.
Using State
Another feature we gain by using redux-thunk is access to the state when processing the action. This
allows us to dispatch or suppress actions according to the current application state. For example, we
can prevent actions from trying to add recipes with duplicate titles:
Chapter 8. Actions and Action Creators 136
Two new concepts can be seen in this code. We used getState() to get access to the full application
state and used a return statement that made our action creator emit no action at all:
It is important to consider where such checks are performed. While multiple actions might dispatch
recipe-related manipulations, we might think it is best to do the check on the reducer level (as it
is the one modifying the state). Unfortunately, there is no way for the reducer to communicate the
problem back to us, and while it can prevent an action from adding duplicate titles to the list, it cant
dispatch a message out. The only thing a reducer can do in this case is add the error directly to the
state tree.
While this approach might work, it adds complications to the reducer and causes it to be aware of
multiple parts of the state treein our example, not just recipes but also the notifications area
which will make it harder to test and break down to multiple reducers. Thus, it is better to have the
validation logic in actions or middleware.
Testing
Testing redux-thunk-based actions is usually more complicated than testing regular actions, as it
may require the use of mocks and spies. As such, it is recommended to keep the actions as simple
as possible and break them into multiple functions and helpers to ease testing and understanding.
Lets create a sample redux-thunk-based action:
Chapter 8. Actions and Action Creators 137
Our action will use the dispatch() method to send an action of type 'MY_ACTION' and use the whole
applications state, obtained via getState(), as a payload.
Unlike with regular actions, we cant just compare the return value and verify its a valid object.
Instead, we will need to rely on a new feature, createSpy(). jasmine.createSpy() creates a special
function that records any calls made to it by our application. This allows our test to check if the
function was called, and if so how many times and with what parameters:
1 describe(actions, () => {
2 it('MY_ACTION', () => {
3 const getState = () => 'DATA';
4 const dispatch = jasmine.createSpy();
5 const expectedAction = { type: 'MY_ACTION', payload: getState() };
6
7 myAction()(dispatch, getState);
8
9 expect(dispatch).toHaveBeenCalledWith(expectedAction);
10 })
11 });
In this example we pass the action creator myAction() two mocked functions: a getState() function
that returns a const value and a mocked dispatch(). Thus, after calling the action creator, we can
verify that the dispatch() function was called correctly and a valid action was passed.
redux-actions
When you start writing a large number of actions, you will notice that most of the code looks the
same and feels like a lot of boilerplate. There are multiple third-party libraries to make the process
Chapter 8. Actions and Action Creators 138
easier and cleaner. The redux-actions library is one of the recommended ones, as it is both simple
and FSA-compliant. The library allows us to easily create new actions using the newly provided
createAction() function. For example:
actions/recipes.js
This code generates an action creator that will return an FSA-compliant action. The generated action
creator will have functionality similar to this function:
1 function addRecipe(title) {
2 return {
3 type: ADD_RECIPE,
4 payload: {
5 title
6 }
7 };
8 }
If the title is our only payload, we could simplify the call by omitting the second argument:
https://github.com/acdlite/redux-actions
Chapter 8. Actions and Action Creators 139
1 function addRecipe(title) {
2 return {
3 type: ADD_RECIPE,
4 payload: {
5 title
6 }
7 };
8 }
We could also pass metadata to the FSA action. For that, we could include a metadata object as a
third argument:
Passing metadata
Or, instead of a metadata object, we could use a function to calculate the metadata based on the
parameters we pass in the payload:
Dynamic metadata
The usage of the action creator is the same as before. We simply call it with the desired parameters:
Chapter 8. Actions and Action Creators 140
1 dispatch(addRecipe('Belgian Waffles'));
2
3 // The dispatched object:
4 {
5 type: 'ADD_RECIPE',
6 error: false,
7 payload: {
8 title: 'Belgian Waffles'
9 },
10 meta: {
11 silent: true,
12 notifyAdmin: false
13 }
14 }
Errors
The action creator will automatically handle errors for us if passed an Error object. It will generate
an object with error = true and the payload set to the Error object:
Dispatching errors
createAction() Example
Heres a full example of using createAction():
Chapter 8. Actions and Action Creators 141
Dispatching errors
In this example, we use the fetch promise to determine whether to create a successful action or an
error one:
The redux-promise library can automatically dispatch FSA-compliant actions and set the error and
payload fields for us. It adds the ability to dispatch() a promise (not just an object or function) and
knows how to automatically handle the resolve and reject functionality of promises:
https://github.com/acdlite/redux-promise
Chapter 8. Actions and Action Creators 142
The magic part here is that we pass a promise to addRecipe() that will take care of creating the
appropriate FSA-compliant action depending on whether the promise is resolved or rejected:
Our only use of then() is to convert the data we get from fetch() into JSON:
This line doesnt return data, but only modifies the promise that we return from the action creator
and that the caller will send to dispatch().
Summary
In this chapter we covered action creators, the fuel running our applications engine. We saw
that Redux is all about simplicityactions are plain objects and action creators are just functions
returning plain objects.
We also saw how we can benefit from ES2016 by dramatically simplifying our syntax.
In the next chapter we will look at reducers, the components of our Redux application that respond
to actions.
Chapter 9. Reducers
The word reducer is commonly associated in computer science with a function that takes an array
or object and converts it to a simpler structurefor example, summing all the items in an array. In
Redux, the role of the reducer is somewhat different: reducers create a new state out of the old one,
based on an action.
In essence, a reducer is a simple JavaScript function that receives two parameters (two objects) and
returns an object (a modified copy of the first argument):
Reducers in Redux are pure functions, meaning they dont have any side effects such as changing
local storage, contacting the server, or saving any data in variables. A typical reducer looks like this:
Basic reducer
Reducers in Practice
In Redux, reducers are the final stage in the unidirectional data flow. After an action is dispatched
to the store and has passed through all the middleware, reducers receive it together with the current
state of the application. Then they create a new state that has been modified according to the action
and return it to the store.
Chapter 9. Reducers 144
The way we connect the store and the reducers is via the createStore() method, which can receive
three parameters: a reducer, an optional initial state, and an optional store enhancer (covered in
detail in the Store and Store Enhancers Chapter.
As an example, we will use the application built in Chapter 2a simple Recipe Book application.
Our state contains three substates:
1 switch (action.type) {
2
3 case ADD_RECIPE:
4 /* handle add recipe action */
5
6 case FETCH_RECIPES:
7 /* handle fetch recipes action */
8
9 ...
10 }
Reducer Separation
The obvious solution would be to find a way to split the reducer code into multiple chunks. The
way Redux handles this is by creating multiple reducers. The reducer passed as a first argument to
createStore() is a plain function, and we can extract code out of it to put in other functions.
Splitting and writing reducers becomes a much easier job if we correctly build the structure of the
state in our store. Given our example of the Recipe Book, we can see that we can create a reducer
for each of the substates. Whats more, each of the reducers only needs to know about its part of the
state, and they have no dependency on each other. For example, the recipes reducer only handles
recipe management, like adding and removing recipes, and doesnt care about how ingredients or
the UI states are managed.
Chapter 9. Reducers 145
Following this separation of concerns, we can put each reducer into a different file and have a single
root reducer manage all of them. Another side effect of this approach is that the root reducer will
pass each of its children only the part of the state that it cares about (e.g., the ingredients reducer
will only get the ingredients substate of the store). This ensures the individual reducers cant break
out out of their encapsulation.
Recipes reducer
Ingredients reducer
Root reducer
This approach results in smaller, cleaner, more testable reducers. Each of the reducers receives a
subset of the whole state tree and is responsible only for that, without even being aware of the other
parts of the state. As the project and the complexity of the state grows, more nested reducers appear,
each responsible for a smaller subset of the state tree.
Combining Reducers
This technique of reducer combination is so convenient and broadly used that Redux provides a very
useful function named combineReducers() to facilitate it. This helper function does exactly what
rootReducer() did in our previous example, with some additions and validations:
We can make this code even simpler by using ES2016s property shorthand feature:
In this example we provided combineReducers() with a configuration object holding two keys
named recipes and ingredients. The ES2016 syntax we used automatically assigned the value
of each key to be the corresponding reducer.
It is important to note that combineReducers() is not limited to the root reducer only. As our state
grows in size and depth, nested reducers will be combining other reducers for substate calculations.
Using nested combineReducers() calls and other combination methods is a common practice in
larger projects.
Chapter 9. Reducers 147
Default Values
One of the requirements of combineReducers() is for each reducer to define the default value for
its substate. Both our recipes and ingredients reducers defined the initial state for their subtrees as
an empty object. Using this approach, the structure of the state tree as a whole is not defined in a
single place but rather built up by the reducers. This guarantees that changes to the tree require us
only to change the applicable reducers and do not affect the rest of the tree.
This is possible because when the store is created, Redux dispatches a special action called
@@redux/INIT. Each reducer receives that action together with the undefined initial state, which
gets replaced with the default parameter defined inside the reducer. Since our switch statements do
not process this special action type and simpy return the state (previously assigned by the default
parameter), the initial state of the store is automatically populated by the reducers.
Tree Mirroring
This brings us to an important conclusion: that we want to structure our reducers tree to mimic
the application state tree. As a rule of thumb, we will want to have a reducer for each leaf of the
tree. It would also be handy to mimic the folder structure in the reducers directory, as it will be
self-depicting of how the state tree is structured.
As complicated manipulations might be required to add some parts of the tree, some reducers
might not fall into this pattern. We might find ourselves having two or more reducers process
the same subtree (sequentially), or a single reducer operating on multiple branches (if it needs to
update structures at two different branches). This might cause complications in the structure and
composition of our application. Such issues can usually be avoided by normalizing the tree, splitting
a single action into multiple ones, and other Redux tricks.
While it is most common for a reducer to examine the type property of the action to
determine if it should act, in some cases other parts of the actions object are used. For
example, you might want to show an error notification on every action that has an error
in the payload.
https://github.com/kolodny/redux-create-reducer
Chapter 9. Reducers 148
Removing the case and default statements can make the code easier to read, especially when
combined with the ES2016 property shorthand syntax. The implementation is trivial:
If you are using the redux-actions library described in the previous chapter, you can also use
the handleActions() utility function from that library. It behaves basically the same way as
createReducer(), with one distinctioninitialState is passed as a second argument:
Chapter 9. Reducers 149
If you are using Immutable.js, you might also want to take a look at the redux-immutablejs library,
which provides you with createReducer() and combineReducers() functions that are aware of
Immutable.js features like getters and setters.
Avoiding Mutations
The most important thing about reducers in Redux is that they should never mutate the existing
state. There are a number of functions in JavaScript that can help when working with immutable
objects.
There are a number of ways to detect a change made to a tree, each with its pros and cons. Among
the many solutions, one is to mark where changes were made in the tree. We can use simple methods
like setting a dirty flag, use more complicated approaches like adding a version to each node, or
(the preferred Redux way) use reference comparison.
If you are unsure of how references work, jump ahead to the next two chapters and come
back to this one after.
Redux and its accompanying libraries rely on reference comparison. After the root reducer has run,
we should be able to compare the state at each level of the state tree with the same level on the
previous tree to determine if it has changed. But instead of comparing each key and value, we can
compare only the reference or the pointer to the structure.
In Redux, each changed node or leaf is replaced by a new copy of itself that has the changed data.
Since the nodes parent still points to the old copy of the node, we need to create a copy of it as
well, with the new copy pointing to the new child. This process continues with each parent being
recreated until we reach the root of the tree. This means that a change to a leaf must cause its parent,
the parents parent, etc. to be modifiedi.e., it causes new objects to be created. The following
illustration shows the state before and after it is run through a reducers tree and highlights the
changed nodes.
Chapter 9. Reducers 151
The main reason reference comparison is used is that this method ensures that each reference to the
previous state is kept coherent. We can examine it at any time and get the state exactly as it was
before a change. If we create an array and push the current state into it before running actions, we
will be able to pick any of the pointers to the previous state in the array and see the state tree exactly
as it was before all the subsequent actions happened. And no matter how many more actions we
process, our original pointers stay exactly as they were.
This might sound similar to copying the state each time before changing it, but the reference system
will not require 10 times the memory for 10 states. It will smartly reuse all the unchanged nodes.
Consider the next illustration, where two different actions have been run on the state, and how the
three trees look afterward.
Chapter 9. Reducers 152
The first action added a new node, C3, under B1. If we look closely we can see that the reducer
didnt change anything in the original A tree. It only created a new A object that holds B2 and a
new B1 that holds the original C1 and C2 and the new C3. At this point we can still use the A tree
and have access to all the nodes like they were before. Whats more, the new A tree didnt copy the
old one, but only created some new links that allow efficient memory reuse.
The next action modified something in the B2 subtree. Again, the only change is a new A root
object that points to the previous B1 and the new B2. The old states of A and A are still intact and
memory is reused between all three trees.
Since we have a coherent version of each previous state, we can implement nifty features like undo
and redo (we simply save the previous state in an array and, in the case of undo, make it the current
one). We can also implement more advanced features like time travel, where we can easily jump
between versions of our state for debugging.
What Is Immutability?
The ideas we just discussed are the root of the concept of immutability. If you are familiar with
immutability in JavaScript, feel free to skip the remainder of this discussion and proceed to the
Ensuring Immutability section.
Lets define what the word mutation means. In JavaScript, there are two types of variables: ones
that are copied by value, and ones that are passed by reference. Primitive values such as numbers,
strings, and booleans are copied when you assign them to other variables, and a change to the target
variable will not affect the source:
Chapter 9. Reducers 153
In contrast, collections in JavaScript arent copied when you assign them; they only receive a pointer
to the location in memory of the object pointed to by the source variable. This means that any change
to the new variable will modify the same memory location, which is pointed to by both the old and
new variables:
Collections example
As you can see, the original object is changed when we change the copy. We used const here to
emphasize that a constant in JavaScript holds only a pointer to the object, not its value, and no error
will be thrown if you change the properties of the object (or the contents of an array). This is also
true for collections passed as arguments to functions, as what is being passed is the reference and
not the value itself.
Luckily for us, ES2016 lets us avoid mutations for collections in a much cleaner way than before,
thanks to the Object.assign() method and the spread operator.
The spread operator is fully supported by the ES2016 standard. More information is
available on MDN.
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_operator
Chapter 9. Reducers 154
Objects
Object.assign() can be used to copy all the key/value pairs of one or more source objects into one
target object. The method receives the following parameters:
Since our reducers need to create a new object and make some changes to it, we will pass a new
empty object as the first parameter to Object.assign(). The second parameter will be the original
subtree to copy and the third will contain any changes we want to make to the object. This will
result in us always having a fresh object with a new reference, having all the key/value pairs from
the original state and any overrides needed by the current action:
Example of Object.assign()
1 function reduce(state, action) {
2 const overrides = { price: 0 };
3
4 return Object.assign({}, state, overrides);
5 }
6
7 const state = { ... };
8 const newState = reducer(state, action);
9
10 state === newState; // false!
Deleting properties can be done in a similar way using ES2016 syntax. To delete the key name from
our state we can use the following:
Arrays
Arrays are a bit trickier, since they have multiple methods for adding and removing values. In
general, you just have to remember which methods create a new copy of the array and which change
the original one. For your convenience, here is a table outlining the basic array methods.
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Chapter 9. Reducers 155
Mutating arrays
The basic array operations we will be doing in most reducers are appending, deleting, and modifying
an array. To keep to the immutability principles, we can achieve these using the following methods:
Ensuring Immutability
The bitter truth is that in teams with more than one developer, we cant always rely on everyone
avoiding state mutations all the time. As humans, we make mistakes, and even with the strictest pull
request, code review, and testing practices, sometimes they crawl into the code base. Fortunately,
Chapter 9. Reducers 156
there are a number of methods and tools that strive to protect developers from these hard-to-find
bugs.
One approach is to use libraries like deep-freeze that will throw errors every time someone tries
to mutate a frozen object. While JavaScript provides an Object.freeze() method, it freezes only
the object it is applied to, not its children. deep-freeze and similar libraries perform nested freezes
and method overrides to better catch such errors.
Another approach is to use libraries that manage truly immutable objects. While they add additional
dependencies to the project, they offer a number of benefits as well: they ensure true immutability,
offer cleaner syntax to update collections, support nested objects and provide performance improve-
ments on very large data sets.
The most common library is Facebooks Immutable.js, which offers a number of key advantages
(in addition to many more advanced features):
It is important to carefully consider your state tree before choosing an immutable library. The
performance gains might only become perceptible for a small percentage of the project, and the
library will require all of the developers to understand a new access syntax and collection of methods.
Another library in this space is seamless-immutable, which is smaller, works on plain JavaScript
objects, and treats immutable objects the same way as regular JavaScript objects (though it has
similar convenient setters to Immutable.js). Its author has written a great post where he describes
some of the issues he had with Immutable.js and what his reasoning was for creating a smaller
library.
The last approach is to use special helper functions that can receive a regular object and an
instruction on how to change it and return a new object as a result. There is such an immutability
helper named update(). Its syntax might look a bit weird, but if you dont want to work with
immutable objects and clog object prototypes with new functions, it might be a good option.
Higher-Order Reducers
The power of Redux is that it allows you to solve complex problems using functional programming.
One approach is to use higher-order functions. Since reducers are nothing more than pure functions,
we can wrap them in other functions and create very simple solutions for very complicated problems.
There are a few good examples of using higher-order reducersfor example, for implementing
undo/redo functionality. There is a library called redux-undo that takes your reducer and enhances
it with undo functionality. It creates three substates: past, present, and future. Every time your
reducer creates a new state, the previous one is pushed to the past states array and the new one
becomes the present state. You can then use special actions to undo, redo, or reset the present state.
Using a higher-order reducer is as simple as passing your reducer into an imported function:
https://github.com/kolodny/immutability-helper
https://github.com/omnidan/redux-undo
Chapter 9. Reducers 158
Another example of a higher-order reducer is redux-ignore. This library allows your reducers to
immediately return the current state without handling the passed action, or to handle only a defined
subset of actions.
The following example will disable removing recipes from our recipe book. You might even use it
to filter allowed actions based on user roles:
Testing Reducers
The fact that reducers are just pure functions allows us to write small and concise tests for them. To
test a reducer we need to define an initial state, an action and the state we expect to have. Calling
the reducer with the first two should always produce the expected state.
This idea works best when we avoid a lot of logic and control flow in reducers.
Two things that are often forgotten while testing reducers are testing unknown actions and
ensuring the immutability of initial and expected objects.
Summary
In this chapter we learned about the part of Redux responsible for changing the application
state. Reducers are meant to be pure functions that should never mutate the state or make any
asynchronous calls. We also learned how to avoid and catch mutations in JavaScript.
In the next and final chapter, we are going to talk about middleware, the most powerful entity
provided by Redux. When used wisely, middleware can reduce a lot of code and let us handle very
complicated scenarios with ease.
https://github.com/omnidan/redux-ignore
Chapter 10. Middleware
Middleware are one of Reduxs most powerful concepts and will hold the bulk of our applications
logic and generic service code.
To understand the concept of middleware, its best first to examine the regular data flow in Redux.
Any action dispatched to the store is passed to the root reducer together with the current state to
generate a new one. The concept of middleware allows us to add code that will run before the action
is passed to the reducer.
In essence, we can monitor all the actions being sent to the Redux store and execute arbitrary code
before allowing an action to continue to the reducers. Multiple middleware can be added in a chain,
thus allowing each to run its own logic, one after another, before letting the action pass through.
The basic structure of a middleware is as follows:
This declaration might seem confusing, but it should become clear once its examined step by step. At
its base, a middleware is a function that receives from Redux an object that contains the getState()
and dispatch() functions. The middleware returns back a function that receives next() and in turn
returns another function that receives action and finally contains our custom middleware code.
The getState() and dispatch() methods should be familiar as they are APIs from the Redux store.
The action parameter is the current Redux action being passed to the store. Only the next() function
should look unfamiliar at this point.
It is sometimes incorrectly noted in teaching material that the parameter passed to the
middleware is store (as it appears to have getState() and dispatch()). In practice, its
an object holding only those two APIs and not the other APIs exported by the Redux store.
Chapter 10. Middleware 160
Understanding next()
If we were to build our own implementation of a middleware, we would probably want the ability
to run code both before an action is passed to the reducers and after. One approach would be to
define two different callbacks for before and after.
Redux middleware takes a different approach and gives us the next() function. Calling it with an
action will cause it to propagate down the middleware chain, calling the root reducer and updating
the state of the store. This allows us to add code before and after passing the action to the reducers:
Example of code
1 const logMiddleware => ({ getState, dispatch }) => next => action => {
2 console.log("Before reducers have run");
3 next(action);
4 console.log("After the reducers have run");
5 };
This nifty trick gives us more power than might initially be apparent. Since we are responsible for
calling next() and passing it the action, we can choose to suppress next() in certain conditions or
even modify the current action before passing it on. Failing to call next(action) inside a middleware
will prevent the action from reaching the other middleware and the store.
Folder Structure
A common approach is to keep all our middleware implementations in a middleware directory at
our application root (similar to reducers and actions). As our application grows, we might find
ourselves adding more subdirectories to organize the middleware, usually by functionality (utility
or authorization).
As with software and hardware, we use middleware as both the singular and the plural
form of the word. In some sources, including the code for applyMiddleware() shown in
Chapter 8, you may see middlewares used as the plural form.
middleware/measure.js
To create this middleware we used the time() and timeEnd() console methods that record a
benchmark with the name provided as a string. We start the timing before running an action, using
the action.type as a name. Then, we tell the browser to print the timing after the action is done.
This way we can potentially catch poorly performing reducer implementations.
An interesting thing to note here is that this middleware completely ignores the first parameter (the
object holding getState() and dispatch()), as we simply dont need it for our example.
Connecting to Redux
Adding a middleware to the Redux store can be done only during the store creation process:
The simplest way to connect a middleware to the Redux store is to use the applyMiddleware() store
enhancer available as an API from Redux itself (store enhancers are explained in Chapter 8:
The applyMiddleware() function can receive an arbitrary number of middleware as arguments and
create a chain to be connected to the store:
Chapter 10. Middleware 162
Note that the order of registration is important. The first middleware, in our case
middlewareA, will get the action before middlewareB. And if the code there decides to
modify or suppress the action, it will never reach either middlewareB or middlewareC.
In real-world applications, you may prefer to apply some middleware only in development or pro-
duction environments. For example, our measureMiddleware might output unwanted information
in the live product, and an analyticsMiddleware might report false analytics in development.
Using the spread operator from ES2016, we can apply middleware to the store conditionally:
Async Actions
What makes middleware so powerful is the access to both getState() and dispatch(), as these
functions allow a middleware to run asynchronous actions and give it full access to the store. A
very simple example would be an action debounce middleware. Suppose we have an autocomplete
field, and we want to prevent the AUTO_COMPLETE action from running as the user types in a search
term. We would probably want to wait 500ms for the user to type in part of the search string, and
then run the query with the latest value.
We can create a debounce middleware that will catch any action with the debounce key set in its
metadata and ensure it is delayed by that number of milliseconds. Any additional action of the same
type that is passed before the debounce timer expires will not be passed to reducers but only saved
as the latest action and executed once the debounce timer has expired:
Chapter 10. Middleware 163
Debounce flow
The skeleton of our middleware needs to inspect only actions that have the required debounce key
set in their metadata:
Since we want each action type to have a different debounce queue, we will create a pending object
that will hold information for each action type. In our case, we only need a handle to the latest
timeout for each action type:
Chapter 10. Middleware 164
If there is already a pending action of this type, we cancel the timeout and create a new timeout
to handle this action. The previous one can be safely ignoredfor example, in our case if an ac-
tion { type: 'AUTO_COMPLETE', payload: 'cat' } comes right after { type: 'AUTO_COMPLETE',
payload: 'ca' }, we can safely ignore the one with 'ca' and only call the autocomplete API for
'cat':
1 setTimeout(
2 () => {
3 delete pending[action.type];
4 next(action);
5 },
6 debounce
7 );
Once the timeout for the latest action has elapsed, we clear the key from our pending object and
next() method to allow the last delayed action to finally pass through to the other middleware and
the store:
Chapter 10. Middleware 165
With this basic middleware we have created a powerful tool for our developers. A simple meta
setting on an action can now support debouncing of any action in the system. We have also used
the middlewares support for the next() method to selectively suppress actions. In the Server
Communication Chapter we will learn about more advanced uses of the async flow to handle generic
API requests.
After our access to the server completes successfully, another action will typically be dispatched,
similar to:
One of the reducers will make sure to update the access token in the state, but it is unclear who
is responsible for issuing the two additional actions required: 'FETCH_USER_INFO' and 'FETCH_-
NOTIFICATIONS'.
We could always use complex action creators and the redux-thunk middleware in our login action
creator:
But this might cause a code reuse problem. If in the future we want to support Facebook Connect, it
will require a different action altogether, which will still need to include the calls to 'FETCH_USER_-
INFO' and 'FETCH_NOTIFICATIONS'. And if in the future we change the login flow, it will need to be
updated in multiple places.
A simple solution to the problem is to cause the two actions to be dispatched only after the login is
successful. In the regular Redux flow, there is only one actor that can listen for and react to events
the middleware:
Chapter 10. Middleware 167
Our new code holds the flow in a single place and will allow us to easily support login via Twitter,
Google Apps, and more.
In practice we can combine flows together and add conditions and more complicated logic, as we
have full access to both dispatch() and getState().
There are a few external libraries that try to make flow management easier, such as
redux-saga.
1 store.dispatch(null);
2 > Uncaught TypeError: Cannot read property 'type' of null(...)
https://github.com/yelouafi/redux-saga
Chapter 10. Middleware 168
In this middleware we catch any attempt to send null instead of the action object and dispatch()
a fake { type: 'UNKNOWN' } instead. While this middleware has no practical value, it should be
apparent how we can use the middlewares power to change actions to support any input type.
The famous redux-thunk middleware is in essence the following code:
Simplified redux-thunk
It checks if the action passed is 'function' instead of the regular object, and if it is a function it
calls it, passing dispatch() and getState().
A similar approach is used by other helper middleware that know how to accept the following, and
more:
https://github.com/gaearon/redux-thunk
Chapter 10. Middleware 169
Store setup
1 createStore(reducer,
2 applyMiddleware(
3 middlewareA,
4 middlewareB,
5 middlewareC
6 )
7 );
Calling next(action) within middlewareB will cause the action to be passed to middlewareC and
then the reducer.
Calling dispatch(action) within middlewareB will cause the action to be passed to middlewareA,
then middlewareB, then middlewareC, and finally to the reducer, returning the execution back to
middlewareB.
Calling dispatch() multiple times is a common and valid practice. next() can also be called more
than once, but this is not recommended as any action passed to next() will skip the middleware
before the current one (for example, potentially skipping the logging middleware).
Parameter-Based Middleware
Beyond the middleware weve discussed so far in this chapter, some middleware might be reusable
and support parameters being passed during their creation.
For example, consider the nullMiddleware we created earlier in this chapter:
The 'UNKNOWN' key is hardcoded into our middleware and will not allow easy reuse in our other
projects. To make this middleware more generic, we might want to be able to support arbitrary action
types and use the applyMiddleware() stage to specify how we want our middleware to behave:
Chapter 10. Middleware 170
Customizable middleware
Here we want our nullMiddleware to dispatch 'OH_NO' instead of the default 'UNKNOWN'. To support
this we must turn our middleware into a middleware creator:
1 const nullMiddlewareCreator = param => store => next => action => {
2 next(action !== null ? action : { type: param || 'UNKNOWN' });
3 };
4
5 export default nullMiddlewareCreator;
Now instead of returning the middleware directly, we return a function that creates a middleware
with custom parameters passed in.
This behavior can be further extended and allow for creation of complex middleware as libraries
that can be easily customized when added to the store.
redux-analytics
redux-thunk
redux-logger
https://github.com/markdalgleish/redux-analytics
https://github.com/gaearon/redux-thunk
https://github.com/fcomb/redux-logger
Chapter 10. Middleware 171
Summary
Middleware are an exceptionally versatile and useful part of Redux, and they are commonly used
to hold the most complicated and generic parts of an application. They have access to the current
action, to the store, and to the dispatch() method, giving them more power than any other part of
Redux.
Our exploration of advanced Redux concepts ends here. In the next section you will find links to
materials for further reading and learning. Thank you for reading our book, and dont forget to send
us your feedback at info@redux-book.com. Happy coding!
Further Reading
Despite its small size, the Redux library has had a huge effect on the way developers handle data
management in single-page applications. Its already used in many large production applications
and has grown a large ecosystem around itself.
Today, there are a lot of great materials about Redux all over the Internet. This book attempts to
group together all the best practices for real-world use of Redux and show how to use it correctly
in large applications. But since the web world is constantly moving forward, it is always good to
keep up to date and explore new libraries and methods. In this section of the book, we would like to
mention some good Redux-related sources for further learning.
Resource Repositories
The Redux repository on GitHub has an Ecosystem documentation section where youll find a
curated list of Redux-related tools and resources.
There is also a less curated (and thus much larger) resource catalog managed by the community
called Awesome Redux. This repository contains a good amount of resources related to using Redux
with different libraries such as Angular, Vue, Polymer, and others.
If you are looking for more React/Redux-focused material, Mark Erikson maintains a resource
repository called React/Redux Links. The materials there are separated by difficulty level, and a
broad range of topics are covered (state management, performance, forms, and many others).
The same author also maintains a more Redux-focused resource list called Redux Ecosystem Links,
which has a similar structure.
Useful Libraries
The Redux ecosystem now includes dozens (if not hundreds) of useful libraries that complement
or extend its features. Heres a short list of libraries that have gained widespread popularity and
are strongly recommended for use in large Redux projectswe recommend that you check out the
source code of these libraries to get a deeper understanding of the extensibility of Redux:
https://github.com/reactjs/redux/blob/master/docs/introduction/Ecosystem.md
https://github.com/xgrommx/awesome-redux
https://github.com/markerikson
https://github.com/markerikson/react-redux-links
https://github.com/markerikson/redux-ecosystem-links
Further Reading 173
reselect
If you count the Redux store as a client-side database of your application, you can definitely count
selectors as queries to that database. reselect allows you to create and manage composable and
efficient selectors, which are crucial in any large application.
redux-actions
Written by Redux cocreator Andrew Clark, this library can reduce the amount of boilerplate code
you need to write when working with action creators and reducers. We find it particularly useful
for writing reducers in a more ES2016 fashion, instead of using large switch statements.
redux-undo
If you ever need to implement undo/redo/reset functionality in some parts of your application, we
recommend using this awesome library. It provides a higher-order reducer that can relatively easily
extend your existing reducers to support action history.
redux-logger
redux-logger is a highly configurable middleware for logging Redux actions, including the state
before and after the action, in the browser console. It should only be used in development. Our advice
is to use the collapsed: true option to make the console output more readable and manageable.
redux-localstorage
This reasonably simple store enhancer enables persisting and rehydrating parts of the store in the
browsers localStorage. It does not support other storage implementations, such as sessionStor-
age. Beware that localStorage isnt supported in private mode in some browsers and always check
its performance in large applications.