How to keep React performant.

0 Comments

The number and diversity of web and mobile apps are growing day by day. Much effort is expended on interface design to make it more user-friendly, attractive and memorable. Complex animations, different effects are used to achieve this goal, which in turn increases the app size and there is an urgent need for application performance, especially while the app is loaded at the first time. That's why it's extremely important to take maximum advantage of React optimisation options on the writing front-end stage. In this articles I wrote about those I had used  myself.

1. Functional components

I believe each programmer wants to write as few code lines as possible, so they could handle more tasks. This is allowed by functional сomponents and not only while writing code, also after transpilation, there are fewer lines in comparison with class components. Let`s compare how functional and class components look like after transpilation with Babel. We'll be using "Try it" playground on the babeljs.io site.

const SimpleFunctionalComponent = (props: IProps): JSX.Element => {
	return <p>Frog number {props.number}</p>;
}
Functional React Component
var SimpleFunctionalComponent = function SimpleFunctionalComponent(props) {
	return React.createElement("p", null, "Frog number ", props.number);
};
Functional Component transpiled to ES5 with Babel

Not bad. And now see the difference...

class SimpleClassComponent extends React.Component {
    render() {
    	return <p>Frog number {this.props.number}</p>;
  }
}
React Class Component
var SimpleClassComponent = /*#__PURE__*/ (function(_React$Component) {
	_inherits(SimpleClassComponent, _React$Component);
    
	function SimpleClassComponent() {
        _classCallCheck(this, SimpleClassComponent);
    	return _possibleConstructorReturn(
      		this,
      		_getPrototypeOf(SimpleClassComponent).apply(this, arguments)
		);
	}

	_createClass(SimpleClassComponent, [
        {
			key: "render",
			value: function render() {
				return React.createElement(
					"p",
					null,
					"Frog number ",
					this.props.number
            	);
			}
		}
	]);

	return SimpleClassComponent;
})(React.Component);
Class Component transpiled to ES5 with Babel

Transpiled functional component consists of 3 lines, and transpiled class component consists of 128 lines. There is a bunch of functional components benefits. Fewer code lines, easy reading and testing, functional components allow us to carry out all logic to container. What about the state ask you? This could be resolved with useState hook. Moreover by the React 16.6 realise all class component methods could be replaced by hooks or their combinations.

2. Reusable components

As for me, reusable components is a real magic pill. Despite the fact that writing them requires more energy it saves programmers' time, app size and provides the maintainability. Material UI library can help in this deal. Its components look like small constructor pieces from which a programmer can create special component for his needs and then integrate it throughout the project. The second feature, which can also be attributed to this item, makes programer's hands free is the ability to write custom hooks.

3. useMemo hook

Don't forget about memoization features. useMemo hook is suitable for controlling the color of components, avoiding autocomplete input, data picker, input fields values, different manipulations with table and the most important - it calls the function recalculation only if new property value differs from the previous one. It was used for setting the hour range of a current day to exclude the past time. The code below is a simplified version, we consider the case when a user selects the current day. When the selected date is changed, the range, that determines how many hours are left till the end of the day, runs.

const TimePicker = ({ 
	selectedDate,
	...props
}: ITimePickerProps): JSX.Element => {
    //...
	const hoursRange = useMemo(
		(): string[] => {
			if (!selectedDate) {
				return [];
			}

	const endOfDay      = moment().endOf('day').toDate();
	const currentMoment = moment();
	let range     : IRange | null = null;
	let hoursArray: IHour[] = [];

	range      = moment.range(currentMoment, endOfDay);
	hoursArray = Array.from(range.by('hour'));
	hoursArray = hoursArray.map((m: any) => m.format('hh a'));

	return hoursArray;
    },
    [selectedDate]
    //...
)};
Example of using useMemo hook

4. React.memo

The next one is React.memo. As we didn't have huge components incoming props of which were sent from the parent component with the same value, we refused from it. Because react.memo functions could take more resources than it is needed for re-render small functional components.

5. CreateSelector

The tool like useMemo hook but available in redux is Reselect library. The idea like useMemo hook. A programmer could wrap his selectors in createSelector and then the returned function result will recalculate only if incoming props changes. Otherwise the selector returns the memoized value. For example, let's take the following situation. Four tabs and displays frog`s list on one of them. There is simple selectors in our redux store for this list.

export const getFrogs = (state: IState): IFrog[] => state.frogs ?? []

In this case selector is called every time user click on any tab. After refactoring this selector with reselect library, getFrogs selector is called only once after click on tab where this frogs list is displayed.

export const getFrogs = createSelector(
    (state: IState): IFrog[] => state.frogs ?? [],
    (frogs: IFrog[]): IFrog[] => frogs
);

6. React.lazy and Suspense components

Nowadays nearly all projects use module bundlers like Webpack, Rollup or Browserify. These tools allow you to handle, optimise and bundle project modules and 3rd party modules into one package. As projects become larger, the size of this package increases. If you have a high speed network it is no big deal to download a few megabytes. But don't forget about users with poor network connection and users of mobile devices. Since React 16.6 turned up there is a possibility to use code splitting and dynamic import, to enhance resource downloading speed.

...technique of splitting your code into various bundles which can then be loaded on demand or in parallel – webpack.js.org

Let's see how you can take advantage of this, using the example of an application router. The following listing describes the situation with several, when each of them displays its own container-component.

import as React          from 'react';
import { Route, Switch } from 'react-router-dom';

import { SomeContainer } from '@modules/containers/SomeContainer';

export const routes = (
    <Switch>
        <Route
			path      = "/some-pathname"
			component = {<SomeContainer />}
          	exact
        />
		// ... other application routes goes here ...
	</Switch>
);
Routes component without Suspense

Let's rewrite this code fragment using lazy and Suspense from 'react' library. React.lazy accepts function which should return a Promise, which resolves the component. All lazy-loaded components are wrapping with Suspense.

import React, { lazy, Suspense } from 'react';
import { Route, Switch }         from 'react-router-dom';

import { Loader }                from '@components/Loader/Loader';

const SomeContainer = lazy(() => import('@modules/containers/SomeContainer'));

export const routes = (
    <Suspense fallback={<Loader />}>
        <Switch>
            <Route
				path      = "/first"
				component = {<SomeContainer />}
				exact
            />
			// ... other application routes goes here ...
         </Switch>
    </Suspense>
);
Routes component with Suspense

As you've probably noticed, Loader-component appeared.I have to mention that among all Suspense-component features the greatest one is that you can set the show object by determining fallback property. It could be spinner, message or any other component.

If you’re using Create React App, Next.js, Gatsby, or a similar tool, you will have a Webpack setup out of the box to bundle your app. If you aren’t, you’ll need to setup bundling yourself. For example, see the Installation and Getting Started guides on the Webpack docs. - reactjs.org

Check if code splitting is enabled in module bundler configuration file, for example, in your Webpack config you should have at least these lines:

module.exports = {
    // ...
    optimization: {
        splitChunks: {
            chunks: 'all'
        }
    }
    // ...
};
Code-splitting setting in webpack.config.js

Now Webpack can split up application bundles on several chunks and clients can load them on demand.

Unsplitted application bundle.
Splitted up application bundle.

It greatly speeds up the initial loading of the application, allowing you to download components directly while navigating a route - when they are really needed.

7. React.Fragment

React.Fragment lets you group a list of children without adding an extra node.

const SimpleComponent = (): JSX.Element => (
	<>
		<Children />
		<Children />
		<Children />
	</>
);
React.Fragment with three children components

But for indexing fragment inside map function you should write <React.Fragment> tag.

8. Take from package those methods just you need

Import the whole library, when you need only one method or component, could increase the performance. For example, when we import the whole material-ui, its size is too big, but if we import only needed component, its size will be smaller. There is a trick for material ui. Plugin babel-plugin-import helps to achieve this goal. Now only necessary component is imported:

import { Button } from '@material-ui/core';
Import only Button

As we know moment.js library is also huge. But if all locales aren`t used, you can exclude them by configuring webpack. One line must be added to plugin.js file in project.

new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
This line helps to avoid importing locales

9. Avoid anonymous functions

Anonymous functions are a great way to pass a function prop (especially the one that needs to be invoked with another prop as its parameter). They get a different reference on every render. In order to maintain the same reference to a function that is passed as a prop to a React component, we utilise the useCallback hook to help keep the same reference (as we are using functional components).

const SimpleComponent= (props: IProps): JSX.Element =>{
    const handleOnChange = useCallback(
        (prop) => {
            setFieldValue(props.field.name, prop.value);
        },
	[props.field.name, prop.value]
	);
    return (
    	<ChildComponent onChange={handleOnChange} />
    );
};
Example, how avoid anonymous function

10. Virtualize long data list

As we have the tables with thousands of data rows, we took react-virtualised component as one of solutions. It allows us to take large sets of data, process them just-in-time, and render them with little-to-no jank.

React-virtualised implements virtual rendering with a set of components that basically work in the following way:

  • They calculate which items are visible inside the area where the list is displayed (the viewport).
  • They use a container (div) with relative positioning to absolute position the children elements inside of it by controlling its top, left, width and height style properties.

11. Form optimization

If you use Formik in your project, then you can optimize your form by using <FastField /> instead of <Field />. <FastField /> is fit if your form consists of more than 30 fields or a field has very expensive validation requirements. <FastField /> has the same property as <Field /> and moreover shouldComponent Update() is implemented internally to avoid all additional rerenders.


For example <FastField name=”name”/> will rerender in next cases:

  • values.name, errors.name, touched.name or isSubmitting. This is determined by shallow comparison.
  • new property was added or old one was removed from <FastField name=”name”/>.
  • the name prop changes.

The list above is suitable for any project. Either you use them or not, it's up to you.

Comments