How to keep React performant

0 Comments

The number and variety 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.

We use complex animations and different effects to achieve this goal, which in turn increases the app size. There is an urgent requirement for application performance, especially while the app is loaded for the first time.

That’s the very reason why it’s essential to take maximum advantage of React optimization options on the writing front-end stage. In the following article, I will explain to you all the options I use by myself.

1. Functional components

I believe that each programmer intends to print as few code lines as possible so that they could handle more tasks. This is allowed by functional сomponents, and not only while writing code, but 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 use the “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

A transpiled functional component consists of 3 lines, and the transpiled class component consists of 128 lines. There is a great number of functional components benefits.

Fewer code lines, easy reading and testing, functional components allow us to carry out all logic to the container. What about the state, you might ask? This could be resolved with the useState hook. Besides, all class component methods could be replaced by hooks or their combinations by the React 16.6 release.

2. Reusable components

Reusable parts are a real magic pill. Even though writing them requires more energy, it saves programmers' time, app size, and provides maintainability. A material UI library can assist this deal.

The first feature is that its components look like small constructor pieces from which a programmer can create a particular element for his demands and then integrate it throughout the project. The second feature, which can also be attributed to this item and makes the programmer's hands-free, is the ability to write custom hooks.

3. useMemo hook

Don't you forget about the memoization features? You need the useMemo hook to cache data. Why is it essential? Think of an expensive computed property that requires looping through a vast Array and doing a lot of computations.

Besides, we may have other computed properties that, in turn, depend on the first one. Without caching, we would be executing the first property much more times than it is required!

The example below shows the hour range of a current day to exclude the past time. The code below is a simplified version, and I consider the case when a user selects the present day. When the selected date is changed, the range that determines how many hours is left until 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

You should use this feature attentively. It is appropriate for the components with a large number of properties to hardly ever change. You would better not to wrap small components in React.memo as they can take more resources than needed for component rendering.

5. CreateSelector

The tool like useMemo hook available in redux is the Reselect library. The idea is the same as useMemo hook. A programmer could wrap his selectors in createSelector, and then the returned function result will recalculate only if incoming props change.

Otherwise, the selector returns the memoized value. For instance, let’s take the following case. Four tabs and displays the frog's list on one of them. There are simple selectors in our redux store for this list.

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

In this case, the selector is called every time the user clicks on any tab. After refactoring this selector with the reselect library, getFrogs selector is called only once after a click on the 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, approximately all projects use module bundlers like Webpack, Rollup, or Browserify. These tools allow you to handle, optimize, and bundle project modules and 3rd party modules into one package. As projects become more substantial, 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 connections 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 case, using the example of an application router. The following listing describes the situation with several when each of them displays its 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 the 'react' library. React.lazy accepts a function that should return a Promise, which resolves the component. All lazy-loaded parts 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 noticed, the Loader-component appeared. I have to mention that among all Suspense-component features, the greatest one is that you can set the shown object by determining fallback property. It could be a spinner, message, or any other component.

If you use 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 don’t, you’ll need to set up 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 instance, 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 dramatically 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 fragments inside map function, you should write <React. Fragment> tag.

8. Take from package those methods just you need

Importing the whole library, when you need only one method or component, could increase the performance. For example, when we import the entire material-UI, its size is too big, but if we import only the required component, its size is smaller.

There is a trick for material UI. Plugin babel-plugin-import helps to achieve this goal. Now the only necessary element is imported:

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

The moment.js library is also vast. But if all locales are not used, you can exclude them by configuring a webpack. One line must be added to the plugin.js file in the project.

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

9. Avoid anonymous functions

Anonymous functions are good to pass a function prop (especially the one that needs to be invoked with another accessory as its parameter). They get a different reference on every render.

To maintain the same text to a function that is passed as a prop to a React component, we utilize the useCallback hook to help keeping the same reference (as implement 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 a long data list

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

React-virtualized implements virtual rendering with a set of components that 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 an 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 costly validation requirements. <FastField /> has the same property as <Field /> and, moreover, shouldComponentUpdate() 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.
  • The new property was added, or the old one was removed from <FastField name=” name”/>.
  • The name prop changes.

All in All

Anyway, everything I mentioned above is suitable for any project connected with React. Thus I would be happy if this list of necessary recommendations will be in hand for you and your coding results.

Comments