React, Automatic Redux Providers, and Replicators
Update: This post is outdated. See https://medium.com/@timbur/bridging-the-gap-between-redux-and-the-declarative-nature-of-relay-graphql-tutorial-45a0bf5bd13a.
Previous Update: You can find the condensed version of this post here.
- Relatively future-proof
- Less prone to bugs
- More easily extendable, reusable, understandable, and maintainable
It should go without saying that all of this would not be possible without the many ongoing contributions to open source from some of the world’s best and brightest.
Throughout this article, we’re going to first discuss a handful of different concepts, and then we’re going to create a simple blog application using said concepts. For those of you who would prefer to check out the end result first and/or play around with some boilerplate:
- See https://github.com/loggur/lumbur for the boilerplate.
- See https://github.com/loggur/bloggur for the end result.
- There is also https://github.com/loggur/provider-boilerplate for creating distributable providers.
This is what we’re going to build:
- An application where anyone can write an article using Markdown.
- Entries can be created, updated, and deleted.
- Entries will be replicated (saved) both client-side and server-side and automatically restored when loading the app.
- The state of the app will depend almost entirely on the current URL.
- The server will render the app’s markup upon a full page request.
- The view (what to render; i.e., the React components) of the app will be completely decoupled from the data/action sources (i.e., the providers) which control the view.
- Data and actions will flow in only one direction (otherwise known as Flux) which allows our view to be predictably and efficiently updated.
- The app will be easily theme-able. Only the currently selected theme will be requested client-side.
- In development mode, changes made to our code will be instantly reflected (“hot reloaded”) within the app for both the client and server.
- In production mode, all scripts are minified.
- We’re not going to concern ourselves with separate users and logging in and out just yet. We’ll cover that in another article.
- On the server, we’ll be using flat files which contain the minimum necessary states of each provider’s store. We’ll improve upon this later by using a proper database and cover that in another article as well.
These are the main packages we’ll be using:
- React to allow us to quickly write declarative and reactive components responsible for rendering everything we see within our app.
- Webpack with a few loaders/plugins to efficiently bundle the application and its individual themes.
- Express as our NodeJS server.
- react-redux-provide to allow our React components to automatically share data and efficiently update whenever some action is triggered. (Note: We won’t be directly using Redux within the app, as all of that is handled internally and automatically for us.)
- provide-theme to allow users to choose different themes whose CSS class names are then provided to our React components.
- provide-map to provide our React components with an ES6 Map instance containing our blog entries and/or currently selected entry, along with a predictable set of functions used for creating, updating, and deleting entries.
- redux-replicate to save the state of our providers’ stores and automatically restore them upon initializing the app. redux-replicate-localforage is used on the client, while redux-replicate-fs is used on the server.
The structure of applications built using providers is extremely straightforward! All you really need are the following 3 directories:
- themes (not required but highly recommended)
As you might imagine, this design makes your entire app insanely easy to understand, maintain, and build upon. It’s so simple, yet immensely powerful. With this paradigm:
- You can build applications of any size and complexity. For larger applications, I recommend splitting it up into multiple smaller applications and then composing them into the single larger application. Providers allow you to do this really easily.
- Since your concerns are maximally separated and nearly all of your rendering and controller logic becomes simple functions, unit tests are quicker and easier. With or without tests, you’ll almost certainly see fewer bugs.
- Should any bugs arise, it is relatively easy to track them down and fix them since you can instantly emulate any state of the app at any point.
- It’s easy for teams of developers to integrate each other’s work without worrying about any major conflicts.
- You can distribute standalone components or sets of components.
- You can distribute standalone providers.
- You can distribute standalone themes or allow others to easily create and/or use custom themes.
There are plenty of resources for React components available on the web with a quick search, so I won’t go into much detail there. But for those of you already familiar with React and Redux, I’ll say this: When using providers, you can forget about container components and presentational components. The only thing your components should do is render the view of your app and occasionally trigger actions which are provided via properties. This results in a maximum separation of concerns which makes everything predictable and easy to reason about. It also allows components from one app to be reused within other apps with no conflicts, no potential issues, and no unnecessary dependencies, since all your components become simple functions of their properties.
All you need to do is decorate (i.e., apply a function to) your components with react-redux-provide, which will wrap them with an automatically generated higher-order component designed specifically for each component. This decorator was originally modeled after react-redux’s connect, but has evolved to allow the following:
- Providers are assigned automatically.
- Stores are created automatically.
- You can use any number of stores.
- You can combine providers so that they are aware of each other’s actions.
- You can switch contexts or use multiple contexts at any level.
- Automatically update components more efficiently. This hasn’t been implemented yet, but it should be feasible to map and cache sets of properties to the component instances that depend on them for each provider, which would mean we can eliminate any duplicate calculations when determining which components to update.
- In development mode, you can easily hot reload providers with a single function and the state of your application will remain intact.
The following gist demonstrates the recommended way to decorate a React component with react-redux-provide’s main export:
Now, suppose this HelloWorld component is nested deep within your app. Without providers, you would need to pass the classes property repeatedly throughout all your components from the top component all the way down to your HelloWorld component so that it could know its proper className.
But when using providers, all you need to do is, when mounting the application, pass a theme provider to any component above HelloWorld, and it will automatically receive the classes property, which can be updated at any time.
Some of you might be wondering how the HelloWorld component automatically receives the classes property. Your providers are compared to the wrapped component, and if the wrapped component has propTypes matching some provider’s actions or reducers, the provider will be assigned to the wrapped component. By assigned I mean that the higher-order component (created by the provide function) subscribes to the provider’s internal data store and will update the properties of the wrapped component when the state of the store’s data changes.
To further clarify what’s going on under the hood, in the case where we use a theme provider with our HelloWorld component, what’s happening is that the HelloWorld component is wrapped with a new auto-generated component called ProvideHelloWorld(theme). It of course gets the theme portion of its name from the fact that the theme provider has a classes reducer and the HelloWorld component expects a classes propType, so it knows to match up the theme provider with the HelloWorld component. If multiple providers are matched to the component, their names will be separated by commas — e.g., ProvideHelloWorld(theme,page).
And so your app’s component tree might end up looking something like this after everything is rendered:
Providers are nothing more than simple objects with the following keys:
- actions — See Redux actions.
- reducers — See Redux reducers. Note: For components to properly automatically update upon state changes, your reducers should return a new value — i.e., a quick equality check (state === nextState) should return false.
- merge (stateProps, dispatchProps, parentProps) — This optional but useful function should return an object, which typically adds, removes, or replaces certain provided properties based on whatever logic you deem necessary. For example, in provide-array, if the component has an index property passed to its parent and expects an item property from the provider, the merge function will attempt to provide the item within the array at that index to the component.
- middleware — See Redux middleware. This should be specific to your provider and can be either a single middleware or an array of middlewares.
- enhancer — See Redux store enhancer. This should be specific to your provider and can be either a single enhancer or an array of enhancers.
Here’s an example of a provider in its simplest form:
Each provider should do one thing and do it well, and they should be as encapsulated and independent as possible. You should typically try to keep your action types’ constants contained within the provider, but there are some cases where it may be useful to share constants across multiple providers. In other words, don’t import constants into your components.
Think of providers as an optimal way to share and manipulate data across any number of React components. It’s designed such that they can be instantly swapped out (or upgraded) with other providers of the same shape and everything will still work perfectly. It’s as simple as components declaring, “I want this data and/or these functions to manipulate that data,” and then when initializing the app, all you do is pass the providers to a top level component.
There are two examples I can come up with off the top of my head where you might want to instantly swap one provider for another.
- Suppose you’re building an app designed to use multiple different services which have the same or similar API (e.g., GitHub vs GitLab vs BitBucket, etc.), and the entire state of the app and how it functions depends on the selected service.
Without providers, you would likely have to implement custom logic across multiple components specific to each service, which can quickly become messy and especially time consuming if you want to add more services; it also makes all of your components dependent on specific services.
But when using providers, everything is decoupled and your concerns are separated, which allows for mixing and matching arbitrary components and services, as well as independent distribution. All you need to do is have your components essentially declare, “I want these properties and I want to be able to perform this action for the selected service.” And all providers need to do is ensure they’re using a certain set of action keys and reducer keys.
- Suppose you’re building a game with React where you want players to be able to use their own custom HUD (heads-up display, basically a dashboard for their status and other real-time info).
Without providers, the naive approach might be to use a single HUD object and pass it around throughout all of your components and use it wherever necessary, but similar to the first example, this can get pretty tedious and relatively time consuming. Plus, you would also likely need to implement custom rendering logic so that components know when to re-render.
But when using providers, you simply pass any compatible HUD provider to a top component and the only thing your components should concern themselves with is the relevant actions and reducers, as re-renders are handled automatically and efficiently.
Note: The actions and reducers for each provider should be uniquely named; i.e., you can’t use the same action and reducer keys in multiple providers within the same context since there’s no way to reasonably differentiate them when automatically assigning them to components. So that’s something to keep in mind when you’re naming your actions and reducers.
You typically don’t need to worry about creating or even manipulating stores yourself (especially not within your components), as they’re automatically created for you, but it helps to know what’s going on internally.
Each provider has its own store which is instantiated at its top level — i.e., when a providers property is passed to some component. Each store is an instance of a Redux store, and it contains and updates the current state of your application’s data in typical Redux fashion, by dispatching actions to its reducers, which may or may not update their state based on the actions. The providers are then shared via React’s context throughout all the components that need them so that all of the relevant components use the same provider(s) and store(s).
Note: You can also combine providers so that they use the same store and can listen for each others’ actions. See the Initialization section below.
What’s good about providers is that, unlike most React + Redux applications, we’re able to use multiple stores will little to no effort. It’s actually more efficient to have multiple stores, because it allows components to only re-render when the store(s) they’re subscribed to have changed; whereas with a single store, all components will determine whether or not they should update.
Here’s a quick example of a counter provider used across multiple components:
So let’s briefly examine what will happen in the above example when the IncrementButton is clicked:
- The increment action is dispatched.
- The CurrentCount component is immediately and efficiently updated with the new count, since both of the components are using the same provider (and thus, store).
- That’s it!
It also becomes extremely easy to write unit tests for each module, and not just the components, but the providers as well, since their actions and reducers are simple, predictable functions.
For example, a simple unit test for the count reducer:
Pretty straightforward, right?
I think that as more and more developers begin designing their applications around providers, we’ll see a ton of really cool, useful stuff in the future where we’re able to build apps that work perfectly on their own but can also be extremely quickly and easily integrated with one another. This design will drastically reduce the amount of time and effort it takes to integrate third party services into applications and/or make different applications work with each other.
So for instance, when trying to integrate another service into our apps, rather than tapping into RESTful API endpoints and/or taking days or weeks to decipher some documentation and integrate various libraries, all we would should ever have to do is import the service’s official provider, specify the necessary propTypes on our components, and boom… as props, we instantly have all the data and actions we need to use said service.
And it goes both ways, of course. If you want to expose an API for your app to other developers, simply package and distribute the same provider(s) you’re already using in your own app.
I’ll also add that all of this entire system is designed such that if something better happens to come along to internally support this paradigm, it should be feasible to quickly swap things out. I think Redux will stand the test of time though!
When you design your app with theming in mind, it lends to even further separation of concerns, which is always good!
With provide-theme, you can provide themes as classes, images, and icons (as well as any other properties within your theme object) to your components, and themes can be bundled using whatever you want:
All a theme needs is a bundled CSS file and a bundled JS file containing the class names (as classes) at the very least. The system behind it is designed with code-splitting and namespacing in mind so that you’re only loading the selected theme and without any potential conflicts.
When mounting and rendering the app, since your components are all wrapped with the higher-order component created by the provide decorator, all you have to do is pass a few props to your app:
- providers — Object containing all of the providers you want your components to use. The keys should be the providers’ names.
- providedState — Optional object containing the combined state of each provider’s reducers. The provide decorator will automatically separate the keys into their providers’ stores as necessary. (This was originally called initialState, but providedState is semantically better, especially for server rendering.)
- combinedProviders — Optional object or array of objects with providers which should share the same store. See someCombinedProvider for an example of a provider that depends on another provider (list).
So rendering the app typically looks like this:
It’s also possible to switch contexts and instantiate new providers at any level within the component tree by simply passing another providers property along with the optional providedState and/or combinedProviders.
As for hot reloading providers in development mode, all you have to do is import the reloadProviders function from react-redux-provide and pass your updated providers and combinedProviders (if necessary) props to it, which is easiest to achieve (and good design) if you use a defaultProps module, like so:
Now that we’ve laid out the concepts, best practices, and overall structure of our components and providers, we can start doing some really nifty stuff using enhancers and middleware.
Replication is probably one of the most common and useful enhancements we can make. react-redux-provide includes a few utility methods to help with adding enhancers and middleware, and so it’s super simple to add replication and persistence (or whatever else we can come up with) to our app.
As an example, suppose we’re using provide-theme and want to automatically locally save and restore the selected theme every time it’s changed, we can use redux-replicate and redux-replicate-localforage when mounting the app, like so:
This particular example adds an enhancer to our theme provider to the beginning of its enhancers chain (which internally boils down to Redux’s compose(…enhancers)(createStore)) and allows the state of the theme provider’s themeName reducer to be replicated and persisted to our client-side storage and automatically restored upon loading the app.
You can do some awesome stuff really easily with replication. For example:
- Instantly add real-time functionality to your app by dropping in a socket replicator.
- Instantly swap out one database for another without having to touch any other code within your app.
- Whatever else you can think of!
Routing and Server Rendering
These concepts are two sides of the same coin.
If you’re familiar with building applications with React, you’re almost certainly familiar with react-router. But with the providers paradigm, there is no reason to use it.
Nothing is stopping you from using a designated router with providers, but you’ll find that if you limit your routing logic to your components’ render methods (as a function of your providers’ states), the structure of your application becomes much simpler while you also have more control and flexibility over what exactly is rendered and how.
Restricting your routing logic to providers also results in one less dependency to think about, as your entire application remains within the providers paradigm. Large apps become much easier to manage!
The provide-page package serves the following purposes:
- It gives your components the data and functions they need to render the state of the application based on the current URL, or update it using the History API.
- It can be combined with other providers so that their states might update as a result some URL changing.
- It allows you to very easily create Express middleware which will automatically enable server rendering. The page provider also gives you full control over your document’s headers, status code (e.g., 404), favicon, title, meta, JS files, and CSS files, all within your React components. It will even wait for asynchronous Redux actions to dispatch before rendering and sending the document string.
Note: Said middleware could theoretically be used with other non-Express servers with a bit of extra help, as Express isn’t actually a dependency.
If you’re curious about how exactly all of that works, you should be able to find everything you need to know within the provide-page repository.
Building a Blog Application
Now that we’ve examined all of the concepts necessary to meet the blog application’s design requirements, let’s go over each of them as they apply to our blog app. Hopefully by this point you have a firm grasp on each concept and can imagine how it would all fit together to achieve the desired result.
So let’s start with our React components. We won’t go over each and every one, but we’ll examine a few of them as they relate to providers, replication, and server rendering. Feel free to check out all of the components in Bloggur’s repository.
Now let’s think about what it would take to create, read, update, and delete blog entries. We’ll use an ES6 Map to contain our entries and ensure their order, with the keys as unique URLs, and the values as objects containing their respective entry’s name, href (same as key), and contents. We’ll create a Map provider using provide-map and then add some actions and reducers specific to our needs. And since the selected entry depends on the current URL, we’ll import those action types from provide-page.
Now that we have our entries provider set up, we can use its actions and reducers to make the app functional.
We have an EntryContents component which is responsible for setting the document’s title (and 404 status code if not found) based on the entries provider’s state, as well as displaying the following components:
- If creatingEntry, show the EntryCreator component.
- If editingEntry and selectedEntryKey and selectedEntry exists, show the EntryEditor component.
- If NOT editingEntry and selectedEntryKey and selectedEntry exists, show the properly rendered Markdown.
- If selectedEntryKey and selectedEntry does not exist, show a “Not found!” message.
- If deletedEntry, show a “Deleted entry!” message.
As you can see, we have full control over the rendering logic of this particular portion of our app, which ends up being extremely straightforward. What’s cool about this is that you can have multiple other components anywhere else in the app (or as a result of composing multiple small apps into one large one) that might also rely on the current URL.
Next, let’s examine the EntryCreator component. We’re looking at this because it accepts formData via’s provide-page’s Form component to create entries.
And that’s all there is to creating components and providers that can be reused, extended, and maintained, all while easily working together to form our application!
Creating Server Middleware
The best way to do this is to use a renderApp module for the client, and a renderAppToString module for the server. The renderApp module will be an entry point in our Webpack config and will be responsible for initializing and mounting the app specifically within the browser. The renderAppToString module will be passed to provide-page’s createMiddleware function.
Both renderApp and renderAppToString will use the same defaultProps:
Now let’s take a look at the renderApp module:
And now our renderAppToString module:
While we’re at it, let’s briefly go over our themes and how they’re bundled. You can structure your themes however you want and use whatever method(s) you want to create your CSS as long as it gets bundled into a JS file and a CSS file. The bundled themes should typically have no dependencies, as they’re plain CSS files and a JS files containing classes (at a minimum), and it’s up to you how any other properties (like images and icons) are included and used within your app.
For Bloggur and Lumbur (the boilerplate), we’re using CSS modules by default, which will automatically create our namespaced class names for us. We can then add each theme to our Webpack config as entry points so that it will bundle them alongside the app.
It is also of course possible (and easy!) to use theme bundles from third parties, since it’s just a matter of specifying each bundle’s file locations within the themesFiles module.
We’ve covered a handful of different concepts throughout this article and put them to real-world use by building a simple blog application. The overall design can be used to fulfill almost any project’s requirements, and the result allows for an extremely modular system where almost any portion can be replaced or upgraded with relative ease.
Our components work independently. Our providers work independently. Our themes work independently. Nearly everything is quickly created and easily assembled. The application’s state can be saved and restored automatically by simply dropping in a replicator. And it takes a single simple middleware for the server to render the application and take action just as the client would, all automatically.
It might actually be easiest to learn how everything really works by playing around with the boilerplate and/or the end result. Please don’t hesitate to ask questions, voice your concerns, submit issues and pull requests, or contribute in any way.
I hope by now that you’re excited as I am about the ensuing possibilities!
Follow me on twitter @_timbur and I’ll take it as a sign that I should keep writing. :)