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.
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.
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.
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 Jennifer Young