“turned on MacBook Pro near brown ceramic mug” by Artem Sapegin on Unsplash

Building Scalable Applications using React — Part 1: Choosing the right mix of tools

Parthasarathy S
Sep 6, 2018 · 8 min read

In the series of building scalable applications using frontend frameworks / libraries we started with Angular in my last post. However when we speak about React we have to take a different approach and one post is enough. The reason being React is highly un-opinionated library unlike opinionated framework Angular.

This leads to obvious decision on how to pick the right mix of tools / libraries along with React to build scalable large applications. In ReactJS we always end up asking “What’s the right way to xyz?”, “What’s the right library for abc?”…so let’s try to address it in Part-1.

Key Considerations

  • Scalable to dynamic requirements
  • Modular in nature and open to multiple communication gateways
  • Single Global State management which can be traceable & testable
  • Faster first time load and consistent performance
  • Reactive to environmental changes including communication, infrastructure, bandwidth & devices

Technology Stack

Let us first look at the Tech Stack for React

ReactJS — Technology Stack

You will see from above Tech Stack the flexibility ReactJS provides on the choices to make. This leads to important decision, which is very key for building ReactJS apps, is the choice of tools / libraries. This chapter is exclusively for arriving at that decision before we look at overall solution architecture.

User Interface (UI) — Components

Productivity is key for developers and make sure to start with a Boilerplate,

Start off with a base UI library based on the UX design (bootstrap, material, sematic, flat….). The following UI libraries also provides customizable UI components & layouts to jump start youe app development.

  • material-ui — Popular UI library for React based on Google material design
  • reactstrap — UI components & layouts based on bootstrap 4
  • PrimeReact — Rich UI components including charts, data grid, complex components and many more…
  • More…

The reason this is important and needs special attention when building React applications is the fact that ReactJS core design principle is ‘HTML within JavaScript (JSX)’. Hence a normal loading of CSS/Sass within JavaScript components does not go well with the Architecture & Design of ReactJS and so the performance. Considering ReactJS is lightweight and very flexible, there are many 3rd party tools to style your application & components

  • CSS Modules — Goes well with React as CSS class names, animation names are scoped locally by default and can be written as a separate CSS file.
  • Styled Components — Highly customisable, easy to read, structured, goes well with components and JSX development, enable global, local and component themes
  • TailwindCSS — Utility based CSS frameworks, very easy to create custom CSS, readable, goes well with components and JSX development, enable global, local and component themes
  • React-JSS — Uses much popular JSS (CSS in JS) within React. Widely used by Static Site Generators, Server Side rendering, enable global and component themes

If you are already using any UI base library mention above, then it provides its own methodology for styling. For instance, ‘Material-UI’ uses Styled Components.

Interface

Interface acts as a bridge between components and the middleware ensuring above principles / patterns

This is where our first test of choosing libraries gets in play as ReactJS is just a view library and for routing, models we need to rely on 3rd party libraries. However thankfully it's pretty straight forward

React-Router is the default choice and also recommended by ReactJS team. This is widely used in all React applications. It works well for all kinds of app

Another great alternative is Reach-Router which is fairly new and forked from React-Router. It’s very simple, config based and has most needed features built in including nested routes, relative links, animations, accessibility. It works well for simple-medium apps and where state management patterns like Redux is not used

You can add react-loadable on top to enable lazy-loading of routed components, dynamic imports, code-splitting.

You already see the flexibility of React and benefits it provides to choose what you need based on the requirements

State Management

Designing & maintaining state is critical in frontend and more so for large applications. State also plays a vital role in performance and ensure re-rendering only when needed.

Ideal for small apps with no nested child components and there no need of passing data between from child to parent.

Event for medium-large apps we can use to persist local component state for interactions like pagination, sorting, highlighting…

This is great when we want to store in a global place and share it across components. This way we can avoid passing loads of props to nested components.

Use Context API for common data sharing across components like locale, theme, master data cache which does not change frequently. Keep this store to the minimum as you may except necessary re-rendering if the data changes frequently.

Once your application grows neither React State nor Context API helps in ensuring maintainability & performance. Debugging becomes a nightmare as its difficult trace data flow and updates. Enter Redux / Mobx which provides a pattern to manage application store / state. Choosing between them is simple,

Use Mobx for small-medium application which is lightweight (don’t have complex transactions / logic). You want to quickly jump in and familiar with object oriented concepts. It is un-opinionated, simple and flexible

Use Redux for medium-large application having many data sets to manage and need a single source of truth which is testable and debuggable. It does have a steep learning curve but has great community support and tooling. It is opinionated and follows functional programming paradigm

Business Logic & Middleware

This is the most challenging and complex decision to make in ReactJS. The reason being its a view library and with multiple other libraries to manage state like Redux, middlewares like Redux Saga, Redux Thunk… it is easy to get confused on where to place your business logic. Again, there are multiple options depending on the usage of redux, redux saga, redux thunk or any other middleware.

This is where you typically have your business logic, calling services as this is the first one triggered upon user action and calls reducers to manage state.

Pros

  • Easy to identify by any developer
  • Ideal for simple use cases and business logic

Cons

  • Not possible to make Async calls for multiple requests
  • No access to global state and need to rely on component props for accessing data from state
  • Cannot have a workflow process to cater audit, error handling or even a workflow logic
  • Testing components is a challenge and need to mock action creators

Use Action Creator for business logic for very simple and there is no need for multiple service requests, handling workflow logic, no dependency with global state.

We tend to have our business logic here as this is where we decide on how to manage state. This sounds a logical place as we have have our logic prior to deciding on the new state.

Pros

  • It's easy to write business logic in Reducer
  • Business logic can decide the new state, so no complex data flow

Cons

  • Reducers are synchronous, so async operations possible
  • It negates the design patterns of Redux to just manage state (Pure functions)
  • Cannot dispatch further actions for audit, logging, error handing

Ideally we should not be using Reducers for Business logic. Use it only for learning purpose or your logic is tightly coupled with state

When our use cases becomes medium — complex we start using middlewares and one such commonly used middleware is Thunk. They are interceptors for Action creators and ideal to have your business logic

Pros

  • Easy to identify by any developer and focussed
  • Access to global state

Cons

  • No easy way to cancel pending requests or only take the latest if multiple requests are made
  • Cannot add further interception to augment your actions (multiple actions)
  • Testing them needs to be mocked as there are functions are promise

Use Thunks for Business Logic for medium complexity and there is no need for augmenting multiple actions for logging, error handling, further requests / interceptors.

We start using Redux-Saga for more complex situations, triggering workflows, augmenting actions for multiple requests, error handling, logging. Considering Redux-Saga orchestrates actions and there by reducers, it is the ideal to have your business logic

Pros

  • We can perform cancelling of requests or take latest if multiple requests through ES6 generators
  • Async Orchestrations is possible through Sagas
  • Testing is easy as action creators only dispatch objects and Saga does everything else

Cons

  • Complex to understand and implement unless you are an expert
  • Lots of code to setup and configure your Sagas
  • No interceptions possible

Use Redux-Saga for business logic for medium — complex situations. You know what you are doing and are an expert with Redux

Many of you wouldn’t have heard about this middleware and its fairly new and moreover whitewashed by usage Thunk and Saga. It brings in Thunk, Saga, Redux-Middleware all together and is the best place for your business logic

Benefits

  • Ability to do Async processing and dispatching
  • Ability to intercept actions for error handling, caching, transformation, authorization…
  • Ability to cancel requests and take latest if multiple requests
  • Easy to test and its declarative keeping actions, business logic, interceptors separate

Redux-Logic is a good approach / solution to have your business logic and provides all benefits of Thunk, Saga, Redux-Middleware, Observable all in one package

Communication Gateway

ReactJS is a view library, so we need to rely on 3rd party for API calls. The following libraries can be used based on the requirements,

axios — Promise based http client and is light weight. This is very popular as its root is based on Angular’s http service

apisauce —Light-weight wrapper for axios. It is better structured, flow and can be used in react-native (determine connection issues)

apollo — One and only client for GraphQL based requests

Interceptors / Wrapper (Add-On)

When you are dealing with large data sets which are nested you may want to intercept incoming data, normalize it so that it can be rendered with performance (or else you will have to deal with scanning through multiple lists to display a nested data on the screen, which is not a good design). Thankfully we have a library ‘normalizr’ which does exactly the same.

Moreover, you should not rely on same schema structure of data set received from API, as it may not be optimized / normalized the way UI component needs to be rendered.

References and Inspirations

FiniteLoop

We are a boutique consulting firm focusing on experience design and highly scalable technical architecture

Parthasarathy S

Written by

Eat, Love and Preach clean architecture & clean code.

FiniteLoop

We are a boutique consulting firm focusing on experience design and highly scalable technical architecture

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade