If you haven’t read part 1 yet, click here
What we’ll be using in this part
- Redux (3.*)
- Redux Dev Tools
- ReactJS
Note
Please note that the code-base we work with is an iterative project, so the code you see in previous parts may differ heavily from the code we use now. I apply optimizations when I can. This series is purely meant to shed a light on the challenges of setting up an isomorphic stack. Not all parts of the code-base will be discussed.
Introduction
Part 2 will be all about setting up a Redux library in your isomorphic stack. What we’ve accomplished now is pretty basic, we can build a website, but how do we handle our business logic? We need library to handle our dataflow.
For this we’ll be using Redux, a state-container for your app. Coming from a traditional Java background I often compared it to an Observable-pattern, but that doesn’t cover the entire picture.
If you’ve done some React programming, you know about Flux, and the architecture it implies. To quote Dan Abramov (the creator of Redux):
Redux evolves the ideas of Flux, but avoids its complexity by taking cues from Elm.
This article will not explain to you how Redux works in-depth, there are many great articles, and even a free video series, taught by Dan himself.
View all of the code here:
Let’s dive in!
1) The Library
Going on the assumption you (now) know what Redux is, and what it’s meant to accomplish, you should feel the need to split it out of your React architecture.
Why? Redux is a state-container, and is completely decoupled from any framework. This means it is be reusable, and not bound to a framework-specific rules/paradigms/…
Reusability of your core logic across multiple apps is one of the greatest advantages of Redux.
To give an example, currently we are building our UI in ReactJS, but Angular 2 is on the horizon, and you may want to switch out UI-paradigms at some point. Redux has predefined bindings for Angular 2 and React, but you can build bindings for any view library. This is what makes redux so powerful, low coupling and high cohesion.
So let’s start off by setting up our library. The folder structure of our “lib” folder will look like this:
Note: This lib folder could just as easily be an npm module or a git submodule. For this setup I opted to keep the library inside the project since it’s a small architecture. Keep in mind that if your setup grows, you will want to split out the core logic.
So let’s quickly run through this directory structure:
- The “index.js” is our entry point, it will expose all critical methods to setup Redux.
- We will also be writing custom middlewares to support custom actions, promises, etc.
- The modules folder is an architectural decision, basically we’ll be mapping a module to a route. For instance, the Feed component that we made would map to a module feed that would contain the actions, reducers, etc. for that route.
- The store folder will contain the logic to create and maintain our store.
- Utils are just, well utilities or easy accessible functions of reusable functionality.
1.1) Setting up the library
We have 3 important aspects that reflect what our library does: it builds a store, it exposes modules, and it exposes middlewares. Why does it expose modules and middlewares? Well before we answer those questions we should first build a basic example to shed some light on common problems we may come across. Then we’ll focus on how to solve them. First, let’s setup the foundation of our library. (Most optimizations we’re implementing now will be explained in part 3.)
This is the most important part of our library, it contains the function that will initialize our store, modules and middlewares:
Below you’ll see where the logic is contained that actually builds up the redux store (separation of concerns). As you can see above, we also define our dev tools. This is important to initialise the redux dev tools, so we can use them front-end. It’ll all come together by the end:
To wrap it all together, the modules we defined need to be exposed. I find that a module collector is the best way to bundle all the modules together:
You could also just store all of them in an array, but I find that a proper class that keeps them sorted and adds semantic value.
It’s task is fairly simple, when you instantiate a moduleCollector and add a module to it, it will collect the reducers and the actions in for that module in the appropriate arrays.
It will also expose the modules via a get-function. This way each individual module can be accessed, as well as all the reducers/actions for all the modules.
A config is passed to each module that is added, right now this is irrelevant. (pre-optimization)
1.2) Defining a module
A module is linked to a route, so each module will contain 3 things:
- Actions
- Reducers
- Constants
The constants define which possible states an action can take (if it’s asynchronous).
In the previous part of this series we defined a Feed route. If we were to map that to a vanilla redux module, it would look something like this:
Under modules create a folder “feed” in that folder define the following files:
Note
Something important you should note while looking at the reducers, is that the initialization of the state object needs to directly reflect the object type you’re trying to return. If this is not the case react-redux will not update the state correctly.
1.3) Making it all come together
Now it’s time to make it all work again. What do we need to do? Our library is basically all set up, but our app does not use it yet. So let’s try and glue it all together.
1.3.1) React-Redux
We need to be able to connect react to get updates on the redux store. This is were react-redux comes into play. It gives us a <Provider /> component which wraps around our routing and exposes the store to all children.
As you can see above, we also expose the store if WebPack define plugin contains a variable called “process.env.feature.DEV” (or the node backend for SSR), this is a feature flag. On build WebPack will ommit this code, since uglify removes dead code.
By default your node does not understand this variable “feature”, so you have to default it.
1.3.2) Redux Dev Tools
In the “app.js” we also exposed the store so we can implement redux developer tools. These developer tools allow us to view our store state mutations. Effectively allowing you to time-travel through the state of your application, as well as through the user actions that invoked the change.
All possible tools can be found here.
How do we get it up and running? We need to add “devtools.js” to our “utils” folder in lib, it looks like this:
Basically, what this does is build up your “DevTools” component which you need to instantiate in the Redux “compose” function.
We also need to add an entry line in our WebPack config, for our dev tools. You could also wrap it inside your app, but I find, because it’s a separate tool you should load it separately and render it to a different DOM Node:
Note:
This is also important for server side rendering, because your dev tools shouldn’t be rendered on your server.
Now, all we need to do is render it to a DOM node:
Voila, if you were to run the code, you would see the following:
(press ctrl-h):
1.3.3) Making your component talk to Redux
Now that our app has a notion of Redux thanks to the <Provider />, and we have the appropriate Dev Tools to manage the state.
We need to link our “Feed” to store updates.
Remember react-redux? It basically can do the following: on state change, you can map certain store values to your properties. Yes, react-redux updates the properties of your Component, so your component can re-render.
This code is very important, it’s the communication layer between React and Redux. (Specifically the “connect” and “mapStateToProps”)
Typically your component shouldn’t have a notion of state mutation, it should just subscribe to/receive updates. That’s what the connect-method accomplishes, it connects our component to store-updates.
When the store updates each component linked to the state will call it’s “mapToProps” or method. Here’s the nifty part, since React is so smart, if nothing has changed it won’t re-render.
An important differentiation is made here, between “Smart” and “Dumb” components. The “Feed” component above has notion of the store and its updates, therefor it is a smart-component. The <Item /> component is a dumb-component, since it has no notion of the store and only shows data.
1.3.4) Server-side rendering
We’ve adjusted our code-base to implement Redux. However, server-side rendering is unaccounted for. Does it still work?
Yes, it will probably still work, however it won’t render anything useful on the server, since the client would override whatever it served. This is because what comes back from the server, and what the client is actually rendering differs. So React will re-render.
In the end our directory structure looks like this:
Time to review
So let’s sum up what we have right now:
- ReactJS setup
- Server Side rendering: which can be validated by checking “react-checksum-id” on your rendered nodes.
- Redux library
- Redux dev tools
- WebPack development configuration
- Front-end Node server that will serve our application
- Babel Transpilation (ES6/ES7)
- Front-end/Server-side routing
What we’re still aiming for:
- Optimise server-side rendering: We shouldn’t create a store each time, but build it up based on passed actions. We should also have the data in the store from async actions before render.
- Mocha test suite
- Redux library optimisation:
API urls, and environment based configuration/assets should be loaded dynamically - Generalize WebPack configuration for multiple build environments (Production-ready build)
- Redux action creators/helpers: Hook up our feed to an async data flow (twitter feed).
Everything above will be covered in Part 3.
Feedback on the format of these articles is always welcome, it’s hard to find a good flow since I’m trying to keep the codebase up to date, but want to write consistent and clear articles relating to the essence of the stack. Feel free to leave a comment.