The purpose of translating this article is purely to exercise my English reading ability. If there is any ambiguity in the translation, please discuss it and delete it

We know that using Hooks for function components in React makes development easier. However, component functions have their own complexities and pitfalls. As a result, it is sometimes difficult to write functional components that are readable and optimized for reuse. Today, we’ll look at five simple examples to help us do just that.

Memoize Data

Let’s take a look at the SortedListView React component below

import React from 'react';

function SortedListView ({ title, items, comparisonFunc }) {
  const sortedItems = [...items];
  sortedItems.sort(comparisonFunc);
  return (
    <div>
      <h1> {title} </h1>
      <ul>
        {sortedItems.map(item => <li> {item} </li>)}
      </ul>
    </div>
  );
}
Copy the code

This component receives an array of items, sorted and displayed. However, sorting can take a lot of time if the array is too long or the sorting method is too complex. Eventually this can become a bottleneck, because even if the Items array or comparisonFunc doesn’t change, but some other prop or state changes, the component will be reordered on rerendering.

We can improve CPU efficiency by recording the sorting method and reordering only when items change. This can be easily done using the useMemo Hook:

import React, { useMemo } from 'react';

function SortedListView ({ title, items, comparisonFunc }) {
  const sortedItems = useMemo(() = > {
    const sortedItems = [...items];
    sortedItems.sort(comparisonFunc);
    return sortedItems;
  }, [items, comparisonFunc]);
  return (
    <div>
      <h1> {title} </h1>
      <ul>
        {sortedItems.map(item => <li> {item} </li>)}
      </ul>
    </div>
  );
}
Copy the code

Therefore, we can use useMemo to store or cache costly operations through partial memory.

Memoize Callback Functions

Just like caching data, we can also cache callbacks that a component passes to other components it renders. The advantage of this is that it prevents unnecessary re-rendering in some cases. To illustrate this, let’s look at how the SortController component uses the SortedListView component:

const { useMemo, useState } = React function SortController ({ items }) { const [isAscending, setIsAscending] = useState(true); const [title, setTitle] = useState(''); const ascendingFn = (a, b) => a < b ? -1 : (b > a ? 1:0); const descendingFn = (a, b) => b < a ? -1 : (a > b ? 1:0); const comparisonFunc = isAscending ? ascendingFn : descendingFn; return ( <div> <input placeholder='Enter Title' value={title} onChange={e => setTitle(e.target.value)} /> <button onClick={() => setIsAscending(true)}> Sort Ascending </button> <button onClick={() => setIsAscending(false)}> Sort Descending </button> <SortedListView title={title} items={items} comparisonFunc={comparisonFunc} /> </div> ); } function SortedListView ({ title, items, comparisonFunc }) { const sortedItems = useMemo(() => { const sortedItems = [...items]; sortedItems.sort(comparisonFunc); return sortedItems; }, [items, comparisonFunc]); return ( <div> <h1> {title} </h1> <ul> {sortedItems.map(item => <li> {item} </li>)} </ul> </div> ); } const items= [5,6,2,100,4,23,12,34] reactdom.render (<SortController items={items} />, document.querySelector('#root') )Copy the code

In the example above, if you go to the “Results” page and enter a new title, it will cause SortController to re-render. As a result, ascendingFn and descendingFn will be rebuilt. This will cause the comparisonFunc to change, and since useMemo in the SortedListView relies on the comparisonFunc, it will reorder even if the comparisonFunc has not changed logically.

We can solve this problem by wrapping ascendingFn and descendingFn with useCallback. This Hook is used to cache the callback function. Remember, we don’t need to pass anything in the dependency array of useCallback at this point, because they don’t depend on anything in the component. (jsfiddle address)

const { useMemo, useState, useCallback } = React function SortController ({ items }) { const [isAscending, setIsAscending] = useState(true); const [title, setTitle] = useState(''); const ascendingFn = useCallback( (a, b) => a < b ? -1 : (b > a ? 1:0), []); const descendingFn = useCallback( (a, b) => b < a ? -1 : (a > b ? 1:0), []); const comparisonFunc = isAscending ? ascendingFn : descendingFn; return ( <div> <input placeholder='Enter Title' value={title} onChange={e => setTitle(e.target.value)} /> <button onClick={() => setIsAscending(true)}> Sort Ascending </button> <button onClick={() => setIsAscending(false)}> Sort Descending </button> <SortedListView title={title} items={items} comparisonFunc={comparisonFunc} /> </div> ); } function SortedListView ({ title, items, comparisonFunc }) { const sortedItems = useMemo(() => { const sortedItems = [...items]; sortedItems.sort(comparisonFunc); return sortedItems; }, [items, comparisonFunc]); return ( <div> <h1> {title} </h1> <ul> {sortedItems.map(item => <li> {item} </li>)} </ul> </div> ); } const items= [5,6,2,100,4,23,12,34] reactdom.render (<SortController items={items} />, document.querySelector('#root') )Copy the code

(95) Decoupling does not Rely on the Component.

Another improvement we can make is to move the ascendingFn and descendingFn from the above function outside of SortController. Because these two functions don’t depend on anything inside. Therefore, there is no need to declare them inside the component, which would be more readable if we did. And we don’t need to use useCallback anymore, because the function won’t be rebuilt during re-rendering. (jsfiddle address)

const { useMemo, useState, useCallback } = React function SortController ({ items }) { const [isAscending, setIsAscending] = useState(true); const [title, setTitle] = useState(''); const comparisonFunc = isAscending ? ascendingFn : descendingFn; return ( <div> <input placeholder='Enter Title' value={title} onChange={e => setTitle(e.target.value)} /> <button onClick={() => setIsAscending(true)}> Sort Ascending </button> <button onClick={() => setIsAscending(false)}> Sort Descending </button> <SortedListView title={title} items={items} comparisonFunc={comparisonFunc} /> </div> ); } function ascendingFn (a, b) { return a < b ? -1 : (b > a ? 1:0); } function descendingFn (a, b) { return b < a ? -1 : (a > b ? 1:0); } function SortedListView ({ title, items, comparisonFunc }) { const sortedItems = useMemo(() => { const sortedItems = [...items]; sortedItems.sort(comparisonFunc); return sortedItems; }, [items, comparisonFunc]); return ( <div> <h1> {title} </h1> <ul> {sortedItems.map(item => <li> {item} </li>)} </ul> </div> ); } const items= [5,6,2,100,4,23,12,34] reactdom.render (<SortController items={items} />, document.querySelector('#root') )Copy the code

We can also save utility functions like sort in another file and import them for use.

Create Subcomponents

Creating child components is the way to write optimized, readable React code — the same goes for class components. Subcomponents break up the code base into smaller, easy-to-understand, reusable chunks. This also makes React easier to optimize rendering. Therefore, it is often a good idea to break down large components into small parts.

Create and Reuse Custom Hooks

Just like components, we can also create custom reusable Hooks. Because the code base is broken down into smaller, reusable chunks, this makes the code more readable. In our example, we can put the sorting logic into a custom Hook called useSorted. (jsfiddle address)

const { useMemo, useState, useCallback } = React function SortedListView ({ title, items, comparisonFunc }) { const sortedItems = useSorted(items, comparisonFunc) return ( <div> <h1> {title} </h1> <ul> {sortedItems.map(item => <li> {item} </li>)} </ul> </div> ); } function useSorted (items, comparisonFunc) { return useMemo(() => { const sortedItems = [...items]; sortedItems.sort(comparisonFunc); return sortedItems; }, [items, comparisonFunc]); } function SortController ({ items }) { const [isAscending, setIsAscending] = useState(true); const [title, setTitle] = useState(''); const comparisonFunc = isAscending ? ascendingFn : descendingFn; return ( <div> <input placeholder='Enter Title' value={title} onChange={e => setTitle(e.target.value)} /> <button onClick={() => setIsAscending(true)}> Sort Ascending </button> <button onClick={() => setIsAscending(false)}> Sort Descending </button> <SortedListView title={title} items={items} comparisonFunc={comparisonFunc} /> </div> ); } function ascendingFn (a, b) { return a < b ? -1 : (b > a ? 1:0); } function descendingFn (a, b) { return b < a ? -1 : (a > b ? 1:0); } const items= [5,6,2,100,4,23,12,34] reactdom.render (<SortController items={items} />, document.querySelector('#root') )Copy the code

Conclusion (Conclusion)

These are five simple techniques we can use to write more readable and optimized function components in React. Feel free to share your own tips for writing better functional components.