Menu Search
Jump to the content X X
Smashing Conf New York

You know, we use ad-blockers as well. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. our upcoming SmashingConf New York, dedicated to smart front-end techniques and design patterns.

How To Scale React Applications

We recently released version 3 of React Boilerplate1, one of the most popular React starter kits, after several months of work. The team spoke with hundreds of developers about how they build and scale their web applications, and I want to share some things we learned along the way.

Further Reading on SmashingMag Link

Announcement of react-boilerplate v3 on Twitter6
The tweet that announced7 the release of version 3 of React Boilerplate

We realized early on in the process that we didn’t want it to be “just another boilerplate.” We wanted to give developers who were starting a company or building a product the best foundation to start from and to scale.

Traditionally, scaling was mostly relevant for server-side systems. As more and more users would use your application, you needed to make sure that you could add more servers to your cluster, that your database could be split across multiple servers, and so on.

Nowadays, due to rich web applications, scaling has become an important topic on the front end, too! The front end of a complex app needs to be able to handle a large number of users, developers and parts. These three categories of scaling (users, developers and parts) need to be accounted for; otherwise, there will be problems down the line.

Containers And Components Link

The first big improvement in clarity for big applications is the differentiation between stateful (“containers”) and stateless (“components”) components. Containers manage data or are connected to the state and generally don’t have styling associated with them. On the other hand, components have styling associated with them and aren’t responsible for any data or state management. I found this confusing at first. Basically, containers are responsible for how things work, and components are responsible for how things look.

Splitting our components like this enables us to cleanly separate reusable components and intermediary layers of data management. As a result, you can confidently go in and edit your components without worrying about your data structures getting messed up, and you can edit your containers without worrying about the styling getting messed up. Reasoning through and working with your application become much easier that way, the clarity being greatly improved!

Structure Link

Traditionally, developers structured their React applications by type. This means they had folders like actions/, components/, containers/, etc.

Imagine a navigation bar container named NavBar. It would have some state associated with it and a toggleNav action that opens and closes it. This is how the files would be structured when grouped by type:

	react-app-by-type
		├── css
		├── actions
		│   └── NavBarActions.js
		├── containers
		│   └── NavBar.jsx
		├── constants
		│   └── NavBarConstants.js
		├── components
		│   └── App.jsx
		└── reducers
		    └── NavBarReducer.js

While this works fine for examples, once you have hundreds or potentially thousands of components, development becomes very hard. To add a feature, you would have to search for the correct file in half a dozen different folders with thousands of files. This would quickly become tedious, and confidence in the code base would wane.

After a long discussion in our GitHub issues tracker and trying out a bunch of different structures, we believe we have found a much better solution:

Instead of grouping the files of your application by type, group them by feature! That is, put all files related to one feature (for example, the navigation bar) in the same folder.

Let’s look at what the folder structure would look like for our NavBar example:

	react-app-by-feature
		├── css
		├── containers
		│    └── NavBar
		│        ├── NavBar.jsx
		│        ├── actions.js
		│        ├── constants.js
		│        └── reducer.js
		└── components
		    └── App.jsx

Developers working on this application would need to go into only a single folder to work on something. And they would need to create only a single folder to add a new feature. Renaming is easy with find and replace, and hundreds of developers could work on the same application at once without causing any conflicts!

When I first read about this way of writing React applications, I thought, “Why would I ever do that? The other way works absolutely fine!” I pride myself on keeping an open mind, though, so I tried it on a small project. I was smitten within 15 minutes. My confidence in the code base was immense, and, with the container-component split, working on it was a breeze.

It’s important to note that this doesn’t mean the redux actions and reducers can only be used in that component. They can (and should) be imported and used from other components!

Two questions popped into my head while working like this, though: “How do we handle styling?” and “How do we handle data-fetching?” Let me tackle these separately.

Styling Link

Apart from architectural decisions, working with CSS in a component-based architecture is hard due to two specific properties of the language itself: global names and inheritance.

Unique Class Names Link

Imagine this CSS somewhere in a large application:

.header { /* … */ }
.title {
	background-color: yellow;
}

Immediately, you’ll recognize a problem: title is a very generic name. Another developer (or maybe even the same one some time later) might go in and write this code:

.footer { /* … */ }
.title {
	border-color: blue;
}

This will create a naming conflict, and suddenly your title will have a blue border and a yellow background everywhere, and you’ll be digging into thousands of files to find the one declaration that has messed everything up!

Thankfully, a few smart developers have come up with a solution to this problem, which they’ve named CSS Modules8. The key to their approach is to co-locate the styles of a component in their folder:

	react-app-with-css-modules
		├── containers
		└── components
		     └── Button
		         ├── Button.jsx
		         └── styles.css

The CSS looks exactly the same, except that we don’t have to worry about specific naming conventions, and we can give our code quite generic names:

.button {
	/* … */
}

We then require (or import) these CSS files into our component and assign our JSX tag a className of styles.button:

/* Button.jsx */
var styles = require('./styles.css');

<div className={styles.button}></div>

If you now look into the DOM in the browser, you’ll see <div class="MyApp__button__1co1k"></div>! CSS Modules takes care of “uniquifying” our class names by prepending the application’s name and postpending a short hash of the contents of the class. This means that the chance of overlapping classes is almost nil, and if they overlap, they will have the same contents anyway (because the hash — that is, the contents — has to be the same).

Reset Properties For Each Component Link

In CSS, certain properties inherit across nodes. For example, if the parent node has a line-height set and the child doesn’t have anything specified, it will automatically have the same line-height applied as the parent.

In a component-based architecture, that’s not what we want. Imagine a Header component and a Footer component with these styles:

.header {
	line-height: 1.5em;
	/* … */
}

.footer {
	line-height: 1;
	/* … */
}

Let’s say we render a Button inside these two components, and suddenly our buttons look different in the header and footer of our page! This is true not only for line-height: About a dozen CSS properties will inherit, and tracking down and getting rid of those bugs in your application would be very hard.

In the front-end world, using a reset style sheet to normalize styles across browsers is quite common. Popular options include Reset CSS, Normalize.css and sanitize.css! What if we took that concept and had a reset for every component?

This is called an auto-reset, and it exists as a plugin for PostCSS9! If you add PostCSS Auto Reset10 to your PostCSS plugins, it’ll do this exactly: wrap a local reset around each component, setting all inheritable properties to their default values to override the inheritances.

Data-Fetching Link

The second problem associated with this architecture is data-fetching. Co-locating your actions to your components makes sense for most actions, but data-fetching is inherently a global action that’s not tied to a single component!

Most developers at the moment use Redux Thunk11 to handle data-fetching with Redux. A typical thunked action would look something like this:

/* actions.js */

function fetchData() {
	return function thunk(dispatch) {
		// Load something asynchronously.
		fetch('https://someurl.com/somendpoint', function callback(data) {
			// Add the data to the store.
			dispatch(dataLoaded(data));
		});
	}
}

This is a brilliant way to allow data-fetching from the actions, but it has two pain points: Testing those functions is very hard, and, conceptually, having data-fetching in the actions doesn’t quite seem right.

A big benefit of Redux is the pure action creators, which are easily testable. When returning a thunk from an action, suddenly you have to double-call the action, mock the dispatch function, etc.

Recently, a new approach has taken the React world by storm: redux-saga12. redux-saga utilizes Esnext generator functions to make asynchronous code look synchronous, and it makes those asynchronous flows very easy to test. The mental model behind sagas is that they are like a separate thread in your application that handles all asynchronous things, without bothering the rest of the application!

Let me illustrate with an example:

/* sagas.js */

import { call, take, put } from 'redux-saga/effects';

// The asterisk behind the function keyword tells us that this is a generator.
function* fetchData() {
	// The yield keyword means that we'll wait until the (asynchronous) function
	// after it completes.
	// In this case, we wait until the FETCH_DATA action happens.
	yield take(FETCH_DATA);
	// We then fetch the data from the server, again waiting for it with yield
	// before continuing.
	var data = yield call(fetch, 'https://someurl.com/someendpoint');
	// When the data has finished loading, we dispatch the dataLoaded action.
	put(dataLoaded(data));
}

Don’t be scared by the strange-looking code: This is a brilliant way to handle asynchronous flows!

The source code above almost reads like a novel, avoids callback hell and, on top of that, is easy to test. Now, you might ask yourself, why is it easy to test? The reason has to do with our ability to test for the “effects” that redux-saga exports without needing them to complete.

These effects that we import at the top of the file are handlers that enable us to easily interact with our redux code:

  • put() dispatches an action from our saga.
  • take() pauses our saga until an action happens in our app.
  • select() gets a part of the redux state (kind of like mapStateToProps).
  • call() calls the function passed as the first argument with the remaining arguments.

Why are these effects useful? Let’s see what the test for our example would look like:

/* sagas.test.js */

var sagaGenerator = fetchData();

describe('fetchData saga', function() {
	// Test that our saga starts when an action is dispatched,
	// without having to simulate that the dispatch actually happened!
	it('should wait for the FETCH_DATA action', function() {
		expect(sagaGenerator.next()).to.equal(take(FETCH_DATA));
	});

	// Test that our saga calls fetch with a specific URL,
	// without having to mock fetch or use the API or be connected to a network!
	it('should fetch the data from the server', function() {
		expect(sagaGenerator.next()).to.equal(call(fetch, 'https://someurl.com/someendpoint'));
	});

	// Test that our saga dispatches an action,
	// without having to have the main application running!
	it('should dispatch the dataLoaded action when the data has loaded', function() {
		expect(sagaGenerator.next()).to.equal(put(dataLoaded()));
	});
});

Esnext generators don’t go past the yield keyword until generator.next() is called, at which point they run the function, until they encounter the next yield keyword! By using the redux-saga effects, we can thus easily test asynchronous things without needing to mock anything and without relying on the network for our tests.

By the way, we co-locate the test files to the files we are testing, too. Why should they be in a separate folder? That way, all of the files associated with a component are truly in the same folder, even when we’re testing things!

If you think this is where the benefits of redux-saga end, you’d be mistaken! In fact, making data-fetching easy, beautiful and testable might be its smallest benefits!

Using redux-saga as Mortar Link

Our components are now decoupled. They don’t care about any other styling or logic; they are concerned solely with their own business — well, almost.

Imagine a Clock and a Timer component. When a button on the clock is pressed, we want to start the timer; and when the stop button on the timer is pressed, you want to show the time on the clock.

Conventionally, you might have done something like this:

/* Clock.jsx */

import { startTimer } from '../Timer/actions';

class Clock extends React.Component {
	render() {
		return (
			/* … */
			<button onClick={this.props.dispatch(startTimer())} />
			/* … */
		);
	}
}
/* Timer.jsx */

import { showTime } from '../Clock/actions';

class Timer extends React.Component {
	render() {
		return (
			/* … */
			<button onClick={this.props.dispatch(showTime(currentTime))} />
			/* … */
		);
	}
}

Suddenly, you cannot use those components separately, and reusing them becomes almost impossible!

Instead, we can use redux-saga as the “mortar” between these decoupled components, so to speak. By listening for certain actions, we can react (pun intended) in different ways, depending on the application, which means that our components are now truly reusable.

Let’s fix our components first:

/* Clock.jsx */

import { startButtonClicked } from '../Clock/actions';

class Clock extends React.Component {
	/* … */
	<button onClick={this.props.dispatch(startButtonClicked())} />
	/* … */
}
/* Timer.jsx */

import { stopButtonClicked } from '../Timer/actions';

class Timer extends React.Component {
	/* … */
	<button onClick={this.props.dispatch(stopButtonClicked(currentTime))} />
	/* … */
}

Notice how each component is concerned only with itself and imports only its own actions!

Now, let’s use a saga to tie those two decoupled components back together:

/* sagas.js */

import { call, take, put, select } from 'redux-saga/effects';

import { showTime } from '../Clock/actions';
import { START_BUTTON_CLICKED } from '../Clock/constants';
import { startTimer } from '../Timer/actions';
import { STOP_BUTTON_CLICKED } from '../Timer/constants';

function* clockAndTimer() {
	// Wait for the startButtonClicked action of the Clock
	// to be dispatched.
	yield take(START_BUTTON_CLICKED);
	// When that happens, start the timer.
	put(startTimer());
	// Then, wait for the stopButtonClick action of the Timer
	// to be dispatched.
	yield take(STOP_BUTTON_CLICKED);
	// Get the current time of the timer from the global state.
	var currentTime = select(function (state) { return state.timer.currentTime });
	// And show the time on the clock.
	put(showTime(currentTime));
}

Beautiful.

Summary Link

Here are the key takeaways for you to remember:

  • Differentiate between containers and components.
  • Structure your files by feature.
  • Use CSS modules and PostCSS Auto Reset.
  • Use redux-saga to:
    • have readable and testable asynchronous flows,
    • tie together your decoupled components.

(il, vf, al)

Footnotes Link

  1. 1 https://github.com/mxstbr/react-boilerplate
  2. 2 https://www.smashingmagazine.com/2016/04/consider-react-native-mobile-app/
  3. 3 https://www.smashingmagazine.com/2015/01/basic-test-automation-for-apps-games-and-mobile-web/
  4. 4 https://www.smashingmagazine.com/2016/03/server-side-rendering-react-node-express/
  5. 5 https://www.smashingmagazine.com/2015/05/client-rendered-accessibility/
  6. 6 https://twitter.com/mxstbr/status/732833839140229120
  7. 7 https://twitter.com/mxstbr/status/732833839140229120
  8. 8 https://github.com/css-modules/css-modules
  9. 9 http://postcss.org/
  10. 10 https://github.com/maximkoretskiy/postcss-autoreset
  11. 11 https://github.com/gaearon/redux-thunk
  12. 12 https://github.com/yelouafi/redux-saga

↑ Back to top Tweet itShare on Facebook

Max works as an open source developer at Thinkmill, where he takes care of KeystoneJS and ElementalUI. He's also the creator of react-boilerplate, the co-creator of Carte Blanche and a co-organiser of the React Vienna Meetup. He loves travelling the world, brewing amazing coffee, skiing big mountains and making things on the web.

  1. 1

    Sagas. Sagas all the way, ground breaking for async code in the frontend.

    23
    • 2

      I absolutely agree. I tried them for a TodoMVC-style demo, and absolutely did not get them!

      Then I tried them in a bigger app, and the rest is history. I’m using them in all of my current apps, and share them around as much as possible – they’re the best model for handling long running, asynchronous transactions in frontend apps there is!

      10
  2. 3

    React Boilerplate v3 is really neat. We have been using it in our own project
    dime-ui. Learn a lot from your experience!

    1
  3. 5

    Nice read!
    Question regarding colocating, though: What if multiple Containers rely on the same / similar actions and reducers?

    4
  4. 7

    Where do you put your sagas in terms of colocating?

    2
  5. 10

    I don’t think grouping actions and reducers per component is good advice. Redux is for shared state. Reducers or actions shouldn’t be tied to one particular component, and actions and reducers are not inherently grouped together, even if, initially, you have an action only consumed by one reducer. I see this misunderstanding about actions “belonging” to particular reducers (and vice-versa) a lot, and it’s not the case. It is expected that an action may come to be used in many reducers as you add features.

    4
  6. 12

    Is there possibly an error in the clockAndTimer function?

    Should:
    put(startTimer());

    be:
    call(startTimer());

    1
  7. 14

    Hi Max

    I’m in the process of building my first enterprise level React-Redux application so I’ve kind of bet my reputation against the success of the application and it’s scalability. I was very glad to read your views on the structure of the solution because they aligned exactly with what I’m doing at the moment. I was also very glad I stopped by to hear about CSS Modules and Redux sagas, I’ll definitely be reading further into them.

    Once question I have is that you mentioned that with CSS modules you can provide localised version of CSS resets, what kind of over head does this put on what we are delivering over the wire in terms of CSS? If we have 100’s of modules on a page all with their own CSS resets? Or is PostCSS smart enough to fish out the dupe CSS selectors for us at compile time?

    Finally, I have tried my best to follow the pattern of stateless components and containers but I’m having trouble trying to see the boundaries between the two. For example I’m making a header bar at the moment. The header bar grabs data from the server and hold this data as state. This data is then used to construct the different elements on my menu. This is with the view to making the ability to change these menus at will during runtime, so-to-speak.

    So, in my header, I might have a Company logo image link component that sits next to a component that holds a number of links. Now the styling for the Company logo image and the link-box component style themselves but I need some styling and JSX to put these two components in place within my header bar component, it will be minimal but it’s styling none the less.

    My header bar component you remember is a container component but now I need to add some styling to it. Am I correct in thinking that to stick to this pattern I must make a third stateless component whose purpose is only to position and display the two child components? I suspect this is the case but this just feels a little contrived. What are your thoughts about situations like this cropping up in the wild?

    Cheers
    Zac

    9
  8. 15

    Marcus Nielsen

    September 8, 2016 9:31 pm

    Wow, thanks for the article!
    I’m using RxJS with reducers, but without action types, since each action is its own stream instance. These kind of articles are super helpful since I don’t get to do redux myself.

    Cheers!

    0
  9. 16

    Will be fun when codebase grows and people start removing components with elements imported by other components.

    Also when you thought async was the new paradigm, you need to make async functions look and behave like sync ones.

    Doing a CSS reset for every component in an applications look like a great idea too.

    The Javascript ecosystem never ceases to amaze me.

    6
  10. 17

    react-app-by-feature
    ├── css
    ├── containers
    └── NavBar
    ├── NavBar.jsx
    ├── actions.js
    ├── constants.js
    └── reducer.js
    └── components
    └── App.jsx

    Reducer and comtainers are not a one-to-one relationship, so where do you place one reducer among serveral comtainers?

    3
  11. 18

    Hi Max,
    the links in you description are wrong. I though about buying the two domains to get the strong smashingmagazine-backlinks. But then I thought it is better to tell you:-)

    0
  12. 20

    Hi,

    Good article, but I don’t understand the final part, where are defined stopButtonClicked and startButtonClicked ?

    0
  13. 21

    Thanks for the great article? Do you have any examples of large open-sourced projects (besides React boilerplate) that use the organize-by-feature convention?

    2
  14. 22

    Thanks for the great article! Do you have any examples of large open-sourced projects (besides React boilerplate) that use the organize-by-feature convention?

    0
  15. 23

    On the topic of containers and components, I recently wrote an article in defence of the occasional stateful view component as well as container: React: the case for stateful view components :)

    0
  16. 24

    Great article
    Grouping files by feature also applies to AngularJS

    0

↑ Back to top