Interesting React Topics

Last updated on May 2022

This is a list of some interesting (but less often discussed) topics related to React that I thought were interesting.

React.Children.toArray

React.Children.toArray(children)

Returns the children opaque data structure as a flat array with keys assigned to each child. Useful if you want to manipulate collections of children in your render methods, especially if you want to reorder or slice this.props.children before passing it down.

As well as toArray, there are a few other functions on React.Children:

  • Children.count(children)
  • Children.forEach(children, fn, thisArg?)
  • Children.map(children, fn, thisArg?)
  • Children.only(children)
  • Children.toArray(children)

There are a lot of gotchas and caveats that are worth reading (see their docs)

You can use them to do things like transform children in a component

import { Children } from 'react';

function RowList({ children }) {
  return (
    <div className="RowList">
      {Children.map(children, child =>
        <div className="Row">
          {child}
        </div>
      )}
    </div>
  );
}

Then if you pass in this:

<RowList>
  <p>This is the first item.</p>
  <p>This is the second item.</p>
  <p>This is the third item.</p>
</RowList>

It will render like this:

<div className="RowList">
  <div className="Row">
    <p>This is the first item.</p>
  </div>
  <div className="Row">
    <p>This is the second item.</p>
  </div>
  <div className="Row">
    <p>This is the third item.</p>
  </div>
</div>

Children.map is similar to to transforming arrays with map(). The difference is that the children data structure is considered opaque. This means that even if it’s sometimes an array, you should not assume it’s an array or any other particular data type. This is why you should use Children.map if you need to transform it.

An interesting question is why is the children prop not always an array?

if there is only a single child, then React won’t create an extra array since this would lead to unnecessary memory overhead. As long as you use the Children methods instead of directly introspecting the children prop, your code will not break even if React changes how the data structure is actually implemented.

It is also recommended to use the Children.map fn because "Even when children is an array, Children.map has useful special behavior. For example, Children.map combines the keys on the returned elements with the keys on the children you’ve passed to it. This ensures the original JSX children don’t “lose” keys even if they get wrapped like in the example above."

examples copied from React docs as they do a good job of explaining it in a simple way

flushSync

flushSync lets you force React to flush any updates inside the provided callback synchronously. This ensures that the DOM is updated immediately.

Call flushSync to force React to flush any pending work and update the DOM synchronously.

import { flushSync } from 'react-dom';

flushSync(() => {
  setSomething(123);
});

React will immediately call this callback and flush any updates it contains synchronously. It may also flush any pending updates, or Effects, or updates inside of Effects. If an update suspends as a result of this flushSync call, the fallbacks may be re-shown.


  • flushSync can significantly hurt performance. Use sparingly.
  • flushSync may force pending Suspense boundaries to show their fallback state.
  • flushSync may run pending effects and synchronously apply any updates they contain before returning.
  • flushSync may flush updates outside the callback when necessary to flush the updates inside the callback. For example, if there are pending updates from a click, React may flush those before flushing the updates inside the callback.

When integrating with third-party code such as browser APIs or UI libraries, it may be necessary to force React to flush updates. Use flushSync to force React to flush any state updates inside the callback synchronously:

Example usage of flush sync on the beforeprint event:

import { useState, useEffect } from 'react';
import { flushSync } from 'react-dom';

export default function PrintApp() {
  const [isPrinting, setIsPrinting] = useState(false);
  
  useEffect(() => {
    function handleBeforePrint() {
      flushSync(() => {
        setIsPrinting(true);
      })
    }
    
    function handleAfterPrint() {
      setIsPrinting(false);
    }

    window.addEventListener('beforeprint', handleBeforePrint);
    window.addEventListener('afterprint', handleAfterPrint);
    return () => {
      window.removeEventListener('beforeprint', handleBeforePrint);
      window.removeEventListener('afterprint', handleAfterPrint);
    }
  }, []);
  
  return (
    <>
      <h1>isPrinting: {isPrinting ? 'yes' : 'no'}</h1>
      <button onClick={() => window.print()}>
        Print
      </button>
    </>
  );
}

Read more about it here

memo()'s second argument: arePropsEqual

This is rarely used but when running memo() around a function, there is an optional second parameter: arePropsEqual.

This is a function that returns true if the props are equal (and it can used a cached version of the result of the function call).

If you pass in just one param (the component you're memoizing), there is automatic checks for ensuring if the props are equal. This 2nd param is useful if you know you only need to check specific props. Use of this 2nd param is quite rare and should be avoided unless you know what you're doing.

Example:

const Chart = memo(function Chart({ dataPoints }) {
  // ...
}, arePropsEqual);

function arePropsEqual(oldProps, newProps) {
  return (
    oldProps.dataPoints.length === newProps.dataPoints.length &&
    oldProps.dataPoints.every((oldPoint, index) => {
      const newPoint = newProps.dataPoints[index];
      return oldPoint.x === newPoint.x && oldPoint.y === newPoint.y;
    })
  );
}

If you provide a custom arePropsEqual implementation, you must compare every prop, including functions. Functions often close over the props and state of parent components. If you return true when oldProps.onClick !== newProps.onClick, your component will keep “seeing” the props and state from a previous render inside its onClick handler, leading to very confusing bugs.

Avoid doing deep equality checks inside arePropsEqual unless you are 100% sure that the data structure you’re working with has a known limited depth. Deep equality checks can become incredibly slow and can freeze your app for many seconds if someone changes the data structure later.

unstable_batchedUpdates

Introduced in React 17, unstable_batchedUpdates batches multiple calls to useState. In React 18 all updates are batched anyway, so unstable_batchedUpdates is not needed.

Before react 18 state updates were not batched (unless part of some browser event). So using unstable_batchedUpdates was a way to batch a bunch of state updates.

But like I said, since React18 it isn't needed.

© 2019-2023 a5h.dev.
All Rights Reserved. Use information found on my site at your own risk.