How to start an enterprise project with React and Redux

Amin Jafari
10 min readNov 23, 2017

--

React-Redux-Enterprise

I am the author of React-Redux-Enterprise boilerplate and today I want to talk about starting a highly scalable, large-scaled, enterprise project using React & Redux.

NOTE: This article is about advanced setup, configuration, structure of a large scale project and the logic behind them and is best targeted at mid to senior level front-end developers.

Getting to know the project

What we use

The followings are the technologies we’re gonna use in our project:

Don’t be afraid of the long list, they all match perfectly with each other :)

The Structure and the Concepts

First let’s have a look at how the structure of our project looks like:

React-Redux-Enterprise
|__ app
|____ actions
|____ components
|______ myComponent
|________ __test__
|________ _index.scss
|________ index.js
|______index.js
|____ constants
|____ containers
|______ myContainer
|________ __test__
|________ _index.scss
|________ action.js
|________ epic.js
|________ index.js
|________ reducer.js
|____ elements
|______ myElement
|________ __test__
|________ _index.scss
|________ index.js
|____ epics
|____ hoc
|______ myHOC
|________ __test__
|________ index.js
|____ reducers
|____ stylesheets
|____ main.js
|__ docs
|__ node_modules
|__ public
|__ view

Our root structure’s pretty straight-forward. We have app that contains our application structure, then comes docs which contains the documentation created with documentation.js, node_modules, public which contains our static built files and view which only contains a single index.html file. I only created this directory because I didn’t want our HTML to be mixed with our Javascript files.

Now let’s dive into the app directory.

We have three main concepts around our react components, we have elements that are “dumb or stateless components” which are typically elements like buttons, inputs or icons. Then we have components that are a set of elements (which also can be stateless) which create a functionality like headers, footers or sidenavs. And last but (most definitely) not least are containers that are a set of components and elements put together to create our pages.

The reason we have separated components from elements is that in a large scaled project we can have hundreds of react components, so in order to avoid long lists of react components that only causes confusion and makes it more difficult to maintain and to make reuse-ability of our react components more elegant we have divided our react components based on their size and kind into elements and components.

Now that you know what elements, components and containers mean in our app, the rest is pretty easy. The hoc directory contains all the Higher Order Components we write and constants contains all the constant data we have in our app.

I assume you are already familiar with other concepts like actions, reducers and epics, so we’re gonna skip over their definitions.

The Setup

Let’s take a look at main.js

https://github.com/Amin52J/React-Redux-Enterprise/blob/master/app/main.js

This is where our app is first initialized. We need a Provider to setup react-redux, PersistGate to setup redux-persist, ConnectedRouter to setup react-router-redux v5 and then we have our App container which has the responsibility to render the routes and the shared components between pages (e.g. Header and Footer).

From this point forth, we’re only gonna talk about the tricks and caveats of setting up our project, things that may take a long time to figure out and things that may not be well documented. If the following information weren’t sufficient, I’ll provide more in depth details in another post.

The Store

The store is the configuration heart of our project, it is where everything is configured. Let’s take a look at it before we dive into it:

https://github.com/Amin52J/React-Redux-Enterprise/blob/master/app/constants/store.js

First we are importing the necessary methods.

import {routerReducer} from 'react-router-redux';
import {createStore, applyMiddleware} from 'redux';
import {createEpicMiddleware} from 'redux-observable';
import {persistCombineReducers} from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import {routerMiddleware} from 'react-router-redux';
import createHistory from 'history/createBrowserHistory';

Then the RxJS observable methods, any other observable needed in the project will go here.

// rxjs observables
import {ajax} from 'rxjs/observable/dom/ajax';
import {of} from 'rxjs/observable/of';
// ----------------

We have an HOC called cleanOnUnmount to reset the state of the container when it is unmounted. This is most useful for pages that are linked to themselves (i.e. the route doesn’t change but the content does). It’ll let us avoid duplicate and unnecessary data in our store. I’ll elaborate more on this later on.
Here we’ll import its reducer factory to add the reset reducer to our combined reducer stack.

import hocReducer from '@hoc/cleanOnUnmount/reducer';

Then we are importing all of our reducers and our combined epics.

import * as reducers from '@reducers';
import epics from '@epics';

And finally we import the global RxJS operators, this contains all of the operators we’re gonna need in our project, so any other RxJS operator will go here.

// global definitions
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/takeUntil';
// ------------------

Then we create a history object using history’s createHistory method and we export it to use it in our main.js file. This is how we need to pass the history to react-router v4.
Please note that react-router v4 and react-router-redux < v5 don’t work well with each other. This also applies for react-router-redux v5 and react-router < v4
So it is highly advised to stick to this setup using the latest versions of the two modules.

export const history = createHistory();

Then we have our redux-persist configuration object.

// the persist config
const persistConfig = {
key: 'root',
storage
};

Next we create an epic middleware using redux-observable’s createEpicMiddleware method by passing our combined epics and a configuration object that defines the observable methods’ dependencies.
Please note that more configuration can be set if needed, but this is the general gist of how it needs to be done.

// the epics middleware
const epicMiddleware = createEpicMiddleware(epics, {
dependencies: {
getJSON: ajax.getJSON,
of
}
});

And finally we combine our reducers using redux-persist’s persistCombineReducers method passing our persist config object as the configuration and the set of reducers including the routing reducer. The we apply the middlewares and create our final store.

// the reducers
const combinedReducers = persistCombineReducers(persistConfig, {
...reducers,
routing: routerReducer
});
const reducer = hocReducer(combinedReducers); // adds reset reducer to our apps reducer
const store = createStore(reducer, applyMiddleware(epicMiddleware, routerMiddleware(history)));

The Routes

Let’s have a look at the routes.js file:

https://github.com/Amin52J/React-Redux-Enterprise/blob/master/app/constants/routes.js

We have created another HOC called asyncComponent which gets a function returning a promise of loaded container. The reason we’re doing this is because we want to split our code using webpack’s brilliant feature called chunks to avoid loading unnecessary code, in other words, to access or load components when they’re needed. This hugely improves the loading time of our application. The asyncComponent returns (based on the route) the target component or null if the component is not needed to be loaded.
The rest of the code is a simple Switch component from react-router v4 containing our apps routes.

The Higher Order Components

We already know that asyncComponent is a wrapper for our containers to be loaded when needed, we also know that cleanOnUnmount is a container enhancer which adds the auto reset functionality to the target container which means it’ll reset the state of the container on unmount, all it needs is a single reset action and a reset reducer which if you remember we added to our store before in the store.js file.

We have two other helper HOCs called passPropsToChild and passPropsToChildren which (as their names say) they simply pass any dynamic properties to the child or children of the target container or component.
Please note that these two helper HOCs rerender the child or children every time any property changes so bare that in mind while using them and it is most recommended to use them as less as you can and very cautiously.

Then we have our reduxConnect HOC that simply connects the actions to the target container using react-redux’s connect method. It receives two required and one optional arguments, WrappedComponent or the target component, actions and the optional state which is a mapStateToProps function by default if you want you can pass null if your container or component has actions but is stateless. We also pass the connected container to react-router-dom’s withRouter HOC to have access to the history object in all of our containers.

https://github.com/Amin52J/React-Redux-Enterprise/blob/master/app/hoc/reduxConnect/index.js

The Stylesheets

In our stylesheets directory we have a main.scss file which binds all our stylesheets defined in elements, components and containers directories together with our reset, common and theme sass files. Let’s have a look at it:

https://github.com/Amin52J/React-Redux-Enterprise/blob/master/app/stylesheets/main.scss

In the _reset.scss file we have our css reset rules.
In the _common.scss file we have our shared css rules.
In the _theme.scss file we define our theme using sass variables.
And that’s it for our stylesheets.

Documentations

If you noticed the comments above the methods and classes in our code you probably recognized that we’re using jsdocs syntax to write information about our code. This all compiles to some beautiful documentation pages using documentation.js at the end, using the command yarn doc . The compiled documentations can be seen in the docs directory in the root of the application.

Linting

It is always a great idea to have a set of coding rules defined before starting a project, specially if you’re working with a team on the project. For our project we’re using eslint with a set of predefined best practices.

Testing

One of the most interesting things to be done in every project is writing tests. I know it can be challenging and a bit boring to some people, but trust me an enterprise project without tests is like jumping off a plane without a parachute! But we’re not here to discuss the benefits of writing tests and how to write tests, so we wont go too deep into the subject.
We use jest and enzyme as our testing framework for this project. All tests are written inside each element, component, container or HOC’s directory inside a directory called __test__ and we only use this naming style because it is easily distinguishable from the rest of the files and folders. All of our test files have the extension .test.js
We run our tests using yarn test or yarn test-update to run the tests and update the snapshots.
We have a file in the root of the application called test.setup.js which contains the configuration for enzyme. Since we’re using React v16, we need to pass its adapter to enzyme like shown below:

https://github.com/Amin52J/React-Redux-Enterprise/blob/master/test.setup.js

We have another caveat in our test, the requestAnimationFrame. Because jest uses requestAnimationFrame we need to write a polyfill for it to support non-browser environments. So we’ve written the simple shim in a file called shim.js and we’ve placed it in the root of our application.

https://github.com/Amin52J/React-Redux-Enterprise/blob/master/shim.js

The Almighty Webpack

To be honest I don’t quiet remember what we used to do before webpack, and now that I think about it, it seems impossible to work on a large-scaled project without webpack (or similar tools). Anyways, without further ado let’s jump right into it:

https://github.com/Amin52J/React-Redux-Enterprise/blob/master/webpack.config.js

Plugins

First we setup the plugins we want to use:

  • CopyWebpackPlugin to copy the index.html file in the view directory to the root public directory.
  • ExtractTextPlugin to extract the webpack stuff out of our css files after the compilation of the scss files.
  • LoaderOptionsPlugin to minimize our output files in the production build.
  • DefinePlugin to set the environment for the webpack.
  • CommonsChunkPlugin to split out the vendors from the code.
  • UglifyJsPlugin only if the environment is production to uglify and compress our outputs files.

Entries

We have three main entries:

entry: {
'js/vendors.js': ['react', 'react-dom', 'isomorphic-fetch'],
'js/bundle.js': path.resolve(__dirname, 'app/main.js'),
'css/style.css': path.resolve(__dirname, 'app/stylesheets/main.scss')
}

Vendors are the modules that are used (almost) everywhere.
Bundle is the main layout, the brain of our code which holds all the configuration and setup parts of the app.
Style is the stylesheets of our application.

Output

output: {
path: path.resolve(__dirname, 'public'),
filename: '[name]',
chunkFilename: '[name].js'
}

All of our output files are going to the public directory and all the chunk files (the containers we set up in the routes.js) go to the url we gave them while setting them up.

Alias

I’ve always hated having to address everything relatively, it made our code have some crazy stuff like ../../../../components/myComponent which looks ridiculous. Now with the setup below we can use @components/myComponent in all the files we need them and they’ll be automagically referenced in our code!

resolve: {
alias: {
'@actions': path.resolve('app/actions'),
'@components': path.resolve('app/components'),
'@constants': path.resolve('app/constants'),
'@containers': path.resolve('app/containers'),
'@elements': path.resolve('app/elements'),
'@hoc': path.resolve('app/hoc'),
'@reducers': path.resolve('app/reducers'),
'@epics': path.resolve('app/epics'),
'@root': path.resolve('app')
}
}

Module

We finally load the appropriate loader for each file extension

module: {
rules: [{
exclude: /(node_modules)/,
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'react']
}
}, {
test: /\.scss$/,
loader: ExtractTextPlugin.extract(['css-loader?-minimize', 'postcss-loader', 'sass-loader'])
}, {
test: /\.(woff|woff2|ttf|svg|eot)(\?v=\d+\.\d+\.\d+)?$/,
loader: 'url-loader'
}, {
test: /\.json$/,
loader: 'json-loader'
}]
}

NOTE: We also use postcss to autoprefix our css rules. The configuration of postcss is saved in the postcss.config.js file in the root of the application.

Final Words

There it was, our highly scalable, large-scaled, enterprise React & Redux project. This post focused mainly on explaining the logic behind the structure and how to overcome the caveats in the way of implementing the latest technologies of front-end development (as of November 2017) and the reason we (need to) use them in an enterprise project.
I hope you enjoyed and found it useful. If there’s need for more in depth explanation on how to setup or work with some particular technologies used in this project please let me know so I can talk more about them in other articles.

Now that you’re familiar with the structure, technologies and the logic behind them don’t forget that you can easily use the boilerplate without having to implement them yourself.

About Me

I’m a front-end developer from Tehran, Iran. I’ve been working in the field for over 6 years and been working with react for almost 3 years now. I find learning about new technologies and how the industry’s growing everyday very fascinating and I always do my best to stay up-to-date with the front-end technologies.

--

--

Amin Jafari

I am a front-end web developer working in the field for over 8 years. You can find me on the web by the name Amin52J or my website Amin52J.com