How to useRef to Fix React Performance Issues

Sidney Alcantara who works on Firetable, a product which features a data grid and an adjacent side drawer. When clicking a cell in the table, the side drawer opens with info about the current cell. Initially they lifted up the state, but that caused performance issues:

The problem was whenever the user selected a cell or opened the side drawer, the update to this global context would cause the entire app to re-render. This included the main table component, which could have dozens of cells displayed at a time, each with its own editor component. This would result in a render time of around 650 ms(!), long enough to see a visible delay in the side drawer’s open animation.

After exploring to split the context, or resort to useMemo/React.memo they settled on useRef to solve this.

Good use-case and detailed write-up!

How to useRef to Fix React Performance Issues →

useQueryParams – A React Hook for managing state in URL query parameters

Once you’ve set up a QueryParamProvider high up your component tree, you can start using useQueryParam to get (and set) querystring parameters

import * as React from 'react';
import { useQueryParam, NumberParam, StringParam } from 'use-query-params';

const UseQueryParamExample = () => {
  // something like: ?x=123&foo=bar in the URL
  const [num, setNum] = useQueryParam('x', NumberParam);
  const [foo, setFoo] = useQueryParam('foo', StringParam);

  return (
    <div>
      <h1>num is {num}</h1>
      <button onClick={() => setNum(Math.random())}>Change</button>
      <h1>foo is {foo}</h1>
      <button onClick={() => setFoo(`str${Math.random()}`)}>Change</button>
    </div>
  );
};

export default UseQueryParamExample;

Also ships with an HOC and Render Props approaches.

useQueryParams

useWorker() – Use Web Workers with React Hooks

useWorker() is a js library (with typescript support) that allows you to use the Web Worker Web API, through React Hooks. This library allows you to run the expensive function without blocking the user interface, using a simple syntax that makes use of Promise

import React from "react";
import { useWorker, WORKER_STATUS } from "@koale/useworker";

const numbers = [...Array(5000000)].map(e => ~~(Math.random() * 1000000));
const sortNumbers = nums => nums.sort();

const Example = () => {
  const [sortWorker, { status, kill }] = useWorker(sortNumbers);
  const runSort = async () => {
    const result = await sortWorker(numbers); // non-blocking UI
    console.log("End.");
  };
  return (<button type="button" onClick={runSort}> Run Sort</button>);
};

Installation per NPM/Yarn:

npm install --save @koale/useworker

useWorker()
useWorker() Source (GitHub) →

🤔 Unfamiliar with Web Workers? Check out Handling JavaScript events with Web Workers then!

useImmer – A React Hook to use Immer to manipulate state

useImmer(initialState) is very similar to useState. The function returns a tuple, the first value of the tuple is the current state, the second is the updater function, which accepts an immer producer function, in which the draft can be mutated freely, until the producer ends and the changes will be made immutable and become the next state.

import React from "react";
import { useImmer } from "use-immer";


function App() {
  const [person, updatePerson] = useImmer({
    name: "Michel",
    age: 33
  });

  function updateName(name) {
    updatePerson(draft => {
      draft.name = name;
    });
  }

  function becomeOlder() {
    updatePerson(draft => {
      draft.age++;
    });
  }

  return (
    <div className="App">
      <h1>
        Hello {person.name} ({person.age})
      </h1>
      <input
        onChange={e => {
          updateName(e.target.value);
        }}
        value={person.name}
      />
      <br />
      <button onClick={becomeOlder}>Older</button>
    </div>
  );
}

Also comes with an useImmerReducer Hook.

use-immer →

Related: The previously linked Mutik also uses Immer under the hood.

useInView – A React Hook to work with IntersectionObserver

The react-intersection-observer package is an easy way to work with the Intersection Observer API in React. It comes with both a Hooks, render props and plain children implementation.

import React from 'react'
import { useInView } from 'react-intersection-observer'

const Component = () => {
  const [ref, inView, entry] = useInView({
    /* Optional options */
    threshold: 0,
  })

  return (
    <div ref={ref}>
      <h2>{`Header inside viewport ${inView}.`}</h2>
    </div>
  )
}

Installation per NPM/Yarn:

npm install react-intersection-observer --save

react-intersection-observer

React: You May Not Need Controlled Form Components

To work with forms in React two approaches are to either use Controlled Components or use Uncontrolled Components (as detailed here). Swyx shares a third way of working with forms:

A lower friction way to handle form inputs is to use HTML name attributes. As a bonus, your code often turns out less React specific!

The key is to add an onchange handler onto the form. From there you can access event.currentTarget to get the form, and then add .nameOfTheInput to it.

// 31 lines of code
function NameForm() {
  const handleSubmit = (event) => {
    event.preventDefault();
    if (event.currentTarget.nameField.value === 'secretPassword') {
      alert('congrats you guessed the secret password!')
    } else if (event.currentTarget.nameField.value) {
      alert('this is a valid submission')
    }
  }
  const handleChange = event => {
    let isDisabled = false
    if (!event.currentTarget.nameField.value) isDisabled = true
    if (event.currentTarget.ageField.value <= 13) isDisabled = true
    event.currentTarget.submit.disabled = isDisabled
  }
  return (
    <form onSubmit={handleSubmit} onChange={handleChange}>
      <label>
        Name:
        <input type="text" name="nameField" placeholder="Must input a value"/>
      </label>
      <label>
        Age:
        <input type="number" name="ageField" placeholder="Must be >13" />
      </label>
      <div>
        <input type="submit" value="Submit" name="submit" disabled />
      </div>
    </form>
  );
}

Feels weird to manipulate the form’s submit button disabled state directly 😅

You May Not Need Controlled Form Components →

React: Hooks vs. Render Props vs. Higher-Order Components

Nice post comparing these three approaches and detailing why you should use the version with hooks.

// #1 - Hooks
const MyComponent = () => {
  const mousePosition = useMouse();

  // mousePosition.x, mousePosition.y
}

// #2 - Render Props
const MyComponent = () => {
  return (
    <Mouse>
      {({ x, y }) => {
        // ...
      }}
    </Mouse>
  )
}

// #3 - HOCs
const MyComponent = ({ x, y }) => {
  // ...
}
export default withMouse(MyComponent);

Having been in the React game for quite some time, and having seen all three variants come to life, I agree wholeheartedly with the conclusion:

When deciding between hooks, render props and higher-order components, always go with hooks wherever possible.

React: Hooks vs. Render Props vs. Higher-Order Components →

React useDeepCompareEffect Hook: A useEffect using deep comparison

A custom Hook by Kent C. Dodds (who else?) that might come in handy for “those situations”:

React’s built-in useEffect hook has a second argument called the “dependencies array” and it allows you to optimize when React will call your effect callback. React will do a comparison between each of the values (via Object.is) to determine whether your effect callback should be called.

The problem is that if you need to provide an object for one of those dependencies and that object is new every render, then even if none of the properties changed, your effect will get called anyway.

useDeepCompareEffect is a drop-in replacement for useEffect. It will not do a reference equality check but a deep comparison before trying to run the effect.

Installation per NPM/Yarn

npm install use-deep-compare-effect

useDeepCompareEffect

React Query – Hooks for fetching, caching and updating asynchronous data in React

React Query provides you with a set of hooks to fetch data in React. Think of pages that use pagination, infinite scroll, auto-refetching, etc. It’s backend agnostic, so sources can be REST, GraphQL, etc.

Here’s an example that uses pagination

import React from 'react'
import fetch from '../libs/fetch'

import { usePaginatedQuery } from 'react-query'

export default () => {
  const [page, setPage] = React.useState(0)

  const { status, resolvedData, data, error, isFetching } = usePaginatedQuery(
    ['projects', page],
    (key, page = 0) => fetch('/api/projects?page=' + page)
  )

  return (
    <div>
      {status === 'loading' ? (
        <div>Loading...</div>
      ) : status === 'error' ? (
        <div>Error: {error.message}</div>
      ) : (
        // The data from the last successful page will remain
        // available while loading other pages
        <div>
          {resolvedData.projects.map(project => (
            <p
              style={{
                border: '1px solid gray',
                borderRadius: '5px',
                padding: '.5rem',
              }}
              key={project.id}
            >
              {project.name}
            </p>
          ))}
        </div>
      )}
      <span>Current Page: {page + 1}</span>{' '}
      <button
        onClick={() => setPage(old => Math.max(old - 1, 0))}
        disabled={page === 0}
      >
        Previous Page
      </button>
      <button
        onClick={() => setPage(old => old + 1)}
        disabled={!data || !data.hasMore}
      >
        Next Page
      </button>
      {// Since the data stick around between page requests,
      // we can use `isFetching` to show a background loading
      // indicator since our `status === 'loading'` state won't be triggered
      isFetching ? <span> Loading...</span> : null}{' '}
    </div>
  )
}

Installation per NPM/Yarn

yarn add react-query

React Query (GitHub) →

useEffect(fn, []) is not the new componentDidMount()

Brad Westfall from React Training on why the React hook useEffect(fn, []) is not the same as componentDidMount():

When developers start learning hooks having come from classes, they tend to think “I need to run some code once when we mount, like how componentDidMount() works. Ah, I see that useEffect with an empty dependency array does just that. Okay I know how all this works…”

This way of thinking gets us into trouble in a few ways:

  1. They’re actually mechanically different, so you might not get what you expect if consider them the same (which we talk about below).
  2. Thinking in terms of time, like “call my side effect once on mount” can hinder your learning of hooks.
  3. Refactoring from classes to hooks will not mean you simply replace your componentDidMount’s with useEffect(fn, []).

Very insightful post!

useEffect(fn, []) is not the new componentDidMount()