Taking your React app to the next level

There are already many articles out there that explain each and every JavaScript library that is on the market. The purpose of this article is to point out some sub-optimal approaches that could be solved better, which would allow for better scalability and maintainability.

twitter post about number of javascript frameworks

Before we start

You may have heard that all the cool kids are using React and Redux these days. Being a cool kid yourself, you probably went ahead and got started with create react app, added redux in the mix and created a TODO app as we all have.

In order to gain something from this article, you should be comfortable with the set of tools mentioned above.
Now that we are on the same page, we are going to go ahead and assume that you are interested in learning how to further improve upon the foundations of your React and Redux knowledge.

You could argue that building an app is all about finding the right tool for the job. It may seem counterproductive to use all of these libraries for a small-scale project, and you would be right to think that. However, we operate under the assumption that every engineer is familiar with these tools and that the project can always outgrow the initial expectations.

Last but not least, using a similar tech stack as the initial point for all of our projects makes switching between different projects more efficient.

Now, with that out of the way, let us proceed.

Take your react app to the next level banner

Async

If you are anything like us, you are impressed by the simplicity of Redux's design. It's pure and explicit. Unfortunately, redux is synchronous by nature and doesn't handle asynchronous actions out of the box.

You could imitate it with something along the lines of this:

  ApiClient.getPeople()
    .then(result =>
      this.props.dispatch({ type: 'FETCH_PEOPLE', payload: result }))
}

This way of handling async just doesn’t cut it for us. What if we wanted to work with the same data in different components? For each of those, we would have to copy this logic to the componentDidMount method or fetch the data somewhere at the top of the component tree and pass it down to the appropriate component. We strongly advise against this approach because it pollutes the UI code with business logic and throws separation of concerns right out the window.

Redux thunk to the rescue!

Not really. Don't get us wrong, redux-thunk works great for many simple use cases, but if you aim to build a large app that can scale over time, it may not be enough.

You get some separation from the UI, as the business logic will now live in the action creators. However, now the action creators are filled with business logic, and that is no place for business logic to be. Our aim is to keep our actions pure and simple and extract the side effects elsewhere.

The real rescue

Currently, there are two very solid libraries to handle (asynchronous) side effects. They are redux-saga and redux-observable. Both of these are redux middleware, through which dispatched actions flow.

Redux-saga

In the case of sagas, you create the so-called “sagas” which allow you to listen for actions and react to them. They can also make asynchronous API calls and dispatch other actions, like a success/failure action upon completing the API call, for instance. Redux-saga uses generators to deal with asynchronosity, which in our eyes is the downside of this option, in the light of its competitor’s approach.

Redux-observable

This middleware is a different kind of beast. You basically define pipes through which actions dispatched in your app will flow. These pipes in question are called epics. It is a pure, functional declaration of what will happen, as opposed to the imperative saga. Because it is dealing with streams, it is based on observables (not yet available in EcmaScript, so you might need something like RxJS). The main downside of redux-observable is that you need to be familiar with RxJS, making the learning curve a bit steeper. Once you learn it, however, it’s a powerful tool that you can utilize in many different situations, unlike sagas.

Data mutation in reducers

Redux is functional and pure. This is a nice property to have because of the powerful implications it has. You cannot make the same claim about purity with JavaScript. JavaScript CAN be pure. However, it does not come without extra effort and discipline. The EcmaScript committee has been kind to us so far and has given us some sweet, syntactic sugar to help with pure data operations, namely the object and array destructuring and the spread operator.

Despite that, editing nested JavaScript objects can quickly become a difficult task and may require a lot of code. As plain JavaScript objects are mutable, you are not given any guarantee that an object won’t get mutated somewhere in the code by an inexperienced colleague, which will introduce a hard-to-trace bug. We expect the object references to be different after an operation is performed, which doesn't happen with mutation.

Immutable to the rescue

Facebook has authored a very nice library for the very purpose of storing data in immutable data structures. The library is called ImmutableJS. It allows us to perform pure and efficient operations on our data.

Immutable does not come without its pitfalls though. When working with ImmutableJS data structures, you either have to access properties using getters like this: person.get('name')

Or you have to define immutable records, which requires additional writing on your part, and quite frankly I personally, don’t know anyone who does that. Because we want to keep our components independent of the type of data they are working with, it is common to transform the data to a plain JavaScript object using the toJS method. That way, we are only using the ImmutableJS library to perform pure operations on immutable data structures, because we are losing all of its performance benefits by using toJS. So there goes our argument about performance.

All of these considerations draw us to the conclusion that ImmutableJS is a powerful tool for a specific task, more specifically: performing efficient operations on large data sets.
Generally, dealing with large data sets isn’t the case and the biggest bottleneck, in performance tends to be rendering, so it may be OK to have slightly less performant data operations.

Fun fact. Did you know that in most functional languages operations are pure, and there is no mutation.

For that very reason, we have started dropping ImmutableJS in favor of a functional utility library ramda

It’s like Lodash, but better. Check out (this excellent talk)(https://www.youtube.com/watch?v=m3svKOdZijA) on why you should favor Ramda. Now these libraries are big in size, but there are babel plugins that allow us to only import the functions we are using, eliminating the size factor. ImmutableJS, on the other hand, is very big, and there is nothing you can do about minimizing its size.

Recompose

Recompose is a compilation of higher order components (HOC) for React that allow you to write more declarative, purer components. When writing React components, we favor stateless(functional) over stateful(class) components when given the choice. Oftentimes, however, we need to add some local state to our component, in which case we may need to change the component from the stateless to stateful. Or we may use the withState HOC to achieve a similar result while keeping your components stateless.

One might also be worried about using stateless components if they are internally using a regular React Component, which means a stateless component is potentially less performant than a PureComponent.

Don’t worry, recompose has you covered. You can use the pure HOC to take advantage of that clean stateless syntax and achieve the performance and state capabilities of a PureComponent. The usage is not limited to these use cases. With recompose you can pretty much rewrite every component to a stateless form, and keep the logic out of it.

CSS

Now there are a ton of ways how one can approach writing stylesheets. Writing efficient and maintainable CSS requires experience and discipline. It is very common for stylesheets that belong to a component to be stored in a completely different place. With this approach, navigating a project and maintaining it will quickly become a nightmare.

For these reasons, it is a good idea to keep your CSS files and component's definition in the same folder, making the styles affecting the component isolated.

This still leaves us with a lot of problems to solve. If you are using class names that are too general, then you will quickly encounter conflicts in class names and unexpected behavior will occur. Or maybe you are nesting your CSS inside some kind of .testimonials-page prefix classes. That leads to problems with specificity when trying to override the CSS.

One of the ways out of this is by using a naming methodology such as BEM. That way you end up with very long class names that you have to take time to come up with, and even then, you are not guaranteed to not encounter conflicts in class names..

All of this can be solved with CSS-in-JS. Styled components is our favorite implementation, and we highly encourage you to check it out.

Think before you npm install

There is an excellent talk on this topic, and we suggest you to watch it. It is now easier than ever to install an npm module for everything. But it all adds up to the resulting bundle size.

We are living in a world where fast Internet is by no means a guarantee. You could either have a bad signal, or maybe your user lives in a country where fast internet access is a rare commodity.

In order for our apps to be loaded in a reasonable time on slow networks, and for our users not to abandon the site before it actually manages to load, we NEED to pay close attention to the bundle size of our app. Same goes for optimizing all of our assets and taking advantage of service worker’s caching wherever possible.

There are great tools to help with this:

So the next time you are thinking about installing npm MomentJS, consider looking for a lightweight alternative or implementing what you need yourself.

Share Article
Martin Petyrek

Martin Petyrek

You might also like...