Using React Memo for a More Performant User Experience

Why Use React to Engineer Biology?

Our biologists want to get to the heart of making biology easier to engineer, so building user friendly, intuitive software is essential. React has helped us make delightful and intuitive scientific software. One major way that React has been essential to developing rapidly yet delivering production worthy, intuitive user interactions is through the ability to create reusable components.

Reusable components allow for consistency in user interactions, allowing the user to learn the software more quickly. For example, if a user sees a green button on one page, and understands that button means you can edit an attribute, then it’s very helpful for that same button to appear on multiple pages. It’s essential that our biologists spend less time learning the software, and more time designing biology – React helps us get there.

Reusable components also allow for us to rapidly develop. For example, when Ginkgo announced that we would be spending resources on COVID testing, the software team was tasked with building a new interface to validate and receive incoming samples. Thanks to React reusable components, we already had the parts of the UI that were needed to build a new interface. A new page in our portal was up and running in a matter of days. Reusable components are essential for rapidly developing for a fast moving company.

What is React Memo?

React.memo is a higher order component, which is to say it is a function that will take a given component and turn it into another component. Specifically, React.memo is a higher order component that will “memoize” previous component props and will avoid re-rendering if no props have changed. React.memo is specifically designed for optimization purposes and not for preventing a render.

You might be thinking — React is set up to only re-render if state or props has changed, so why would you need to prevent a re-render if the props have not changed? Imagine a situation in which a parent component has many child components. The parent component controls a useState hook for each value of the child component. The most likely scenario is that you have a form in which each child component is an input of some sort, in which the parent component would like to keep track of the values so that on submit, the values are sent with the appropriate request. Each time the user types into any of the input values, the parent component’s state updates. When a component’s state updates, it triggers a re-render. When the parent component re-renders, it triggers each of the child components to also re-render. Now, any time the user types into any of the forms, the entire form will re-render.

In this situation, the re-renders can be very expensive and cause lag in the entire user experience. Thus, using React.memo is a way in which you can prevent unnecessary re-renders of untouched child components in order to enhance performance optimization.

It’s important to note that in February, React introduced React hooks. While hooks are meant to replace class, there are some higher order components that have been replaced by React hooks as well. useMemo is the React hook for the React.memo higher order component.

So how did we use React Memo?

When a computational biologist starts creating a new biological design, there is a lot of information we must gather to get started. On the Create a New Design page, we have several input forms for the user to fill out. Create a New Design page is a parent component with several child components of our own customized reusable component for a Form Group. Every time a user enters a new text into any of the forms on this page, the whole page re-renders as the parent state changes. This caused a long lag on our page preventing us from achieving our goal for a delightful user experience. Thus, we wrapped our reusable Form Group component in the React memo function to prevent it from re-rendering if the props had not changed. This means that only the form that the user is currently using will re-render while all other child components on the page will remain the same until touched.

Let’s start with an example of the page that has multiple forms. For simplicity sake, we will show a form with a name and description field. However, React Memo should be utilized only when the form is so large as to cause lag. In our production case, we have about 11 forms that a user should fill out on this page.

Form Screenshot

  const [name, setName] = useState('');
  const [description, setDescription] = useState('');

  const nameField = (
    <PortalBaseFormGroup
      label="Design Name"
      required={true}
      useMemo={true}
      input={
            <Input
              type="text"
              value={name || ''}
              onChange={(e) => {
                setName(e.target.value);
              }}
            />}
    />
  );

  const descriptionField = (
    <PortalBaseFormGroup
      label="Description"
      required={true}
      useMemo={true}
      input={
            <Input
              type="text"
              value={description}
              onChange={(e) => {
                setDescription(e.target.value);
              }}
            />}
    />
  );

  return (
    <div className="new-design-form">
      <FormHeader
        title="Create New Design"
      />
        <FormSection
          title="General"
          inputs={
            <React.Fragment>
              {nameField}
              {descriptionField}
            </React.Fragment>
          }
        />
    </div>
  );
};

export default DesignNew;

This parent component will re-render anytime the state changes, which in this case will be any time a user inputs anything into the Name or Description form, defined by onChange. Thus, every form (all child components) will re-render anytime any of the forms are touched. Notice that React Memo was not used in the parent component. Instead, each child component had a props useMemo that was passed in as true so that each child component can determine whether it should re-render with the rest of the parent component.

Let’s examine our reusable child component PortalBaseFormGroup.

import React, { memo } from 'react';

const PortalBaseFormGroup = ({
 label,
 required = false,
 input,
}) => (
 <FormGroup className="portal-form-group">
   <Label>
     <span className="input-label">{label}</span>
     {required && <span className="required-indicator" />}
   </Label>
   <div>{input}</div>
 </FormGroup>
);
 
const MemoizedComponent = memo(
 PortalBaseFormGroup,
 (prevProps, nextProps) => {
   // returning true does not re-render
   if (nextProps.useMemo && nextProps.input.value) {
     return nextProps.input.value === prevProps.input.value
   }
   return false;
 }
);
export default MemoizedComponent;

In the above code, we wrap the PortalBaseFormGroup in a constant named MemoizedComponent. MemoizedComponent which uses React Memo to determine whether the component should re-render. React.memo takes in two arguments, the first being the component that will render, the second being an anonymous function to determine if the component re-renders. If the anonymous function returns true, a re-render will not be triggered. In this case, our logic is simple. We want to prevent re-render only if the useMemo props is passed in as true and the value has not changed.

By simply adding this props and wrapping our component in a React Memo, we were able to remove the lag on the form, and ultimately render the page instantly. We improved performance and achieved our target of a delightful user experience.

(Feature photo by Vera Ivanova on Unsplash)

Posted By