React - Best practices for structuring Reactjs applications

in Technology   —  

At Graph, we have a strong engineering focus on JavaScript application technologies and practices – specifically React, Redux, React router, and Immutable.js

React is an excellent choice for building JavaScript single page applications. Originally developed by Facebook and popularised over recent years, React is our preferred technology choice for SPA's due to its excellent performance and clean component-based architecture.

React structures the user interface into a set of small individual components, each with their own isolated state. The structure makes it very easy to develop reusable components and test them.

One of the key advantages is performance. Rather than manipulate the elements of the page directly, React works with an in-memory representation of the page (the “Virtual DOM”) and copies the changes to the actual page, making for a much faster user experience.

Another advantage is declarative views, which make the code extremely easy to understand. In react, a component is simply a JavaScript function which takes input data (model) and returns what to display (view).

You can read more about why we switched to React from Angular here.

Our key considerations for building React applications

Whenever we develop a React application, our key considerations are

  • To decide what should be components
  • To decide on the structure of the React applications
  • To design the state of the application - i.e. how data affects the state.
  • Performance optimisation
  • Automation and testing

Deciding what should be a component

When we approach the development of a React application, we first develop wireframes and proposed layouts to identify logical blocks. These might include the appliation's navigation, header and footer, along with individual screens.

We then split these blocks into more and more smaller pieces (components) - for example rendering individual items or tiles within a list view. Our decision about what should or should not be a component is simply down to whether that part of the visible screen is meaningful and can be reused in another place or with other data.

How we structure our React applications

We use a modular and highly-structured approach to our applications:

  • All components are separated into folders –this includes all related assets (jsx, styles, images etc) and makes it very easy to understand everything used by each component.
  • Application pages are separated into folders – along with their relevant layouts / partials.
  • Core modules – such as services, utility helpers, redux action creators, reducers, middleware modules etc, are also split in to dedicated folders.

The screenshots below show one of our ReactJS applications.

The principles we use to organise our React-based applications are as follows:

  • All files related to a visual block (including a React component) are stored in a single folder – jsx, js, css/less, svg etc.
  • We define each React component or ES6 module in a single file – aka “Single Responsibility Principle”
  • We use “Higher-Order Components” to separate presentation from complex logic – so that we split React components into dumb (rendering only) and smart (logic without presentation, state management, event handlers) parts whenever possible.  

Managing data / state

A single page application depends on data which can come from many different sources (API data, user inputs, device hardware / sensors etc). A key part of the SPA design is to decide how store and best manage this data.

We load data into the redux store if:

  • it loads from the server (via AJAX requests);
  • it should be rendered in several parts of the page (e.g. message counter and message list);
  • different components will update the same data independently (e.g. for the RAR's top filter line and filters in the sidebar both update filter state).

All authentication information is also stored in the redux store, because any component might rely on this, checking roles / authorization before showing specific data.

The data should be in a local state of the component if only this component uses this data. For example, the following data are a good candidate to be stored in the local state:

  • a value of the input field;
  • a validation result (valid or invalid value);
  • an active tab;
  • a state of a collapsible panel.

Performance optimisations

By default, React will re-render all components, even when data has not changed.

To improve performance, and reduce unnecessary calculation we design our applications to track when components need to be updated – via shouldComponentUpdate()

See: https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate 

And: https://facebook.github.io/react/docs/optimizing-performance.html

DevOps automation

Finally, we couple our best practices with extensive automation, helping us to to deliver React-based applications quickly and effectively

  • JavaScript code is written in a modular fashion with dependencies declared via CommonJS and Asynchronous Module Definition (AMD).
  • We develop all code using ES6 with BabelJS to support older browsers.
  • WebPack is used to compile all JavaScript modules, allowing us to bundle or output multiple files as needed.
  • ESLint is used – our linting rules are available at https://github.com/graph-uk/eslint-config-graph

We use extensive DevOps practices to automate the deployment of any version of any code to any environment - with Jenkins CI to manage and execute our build pipelines.

Read next