Some best practices for using useSelect() from @wordpress/data

During a recent debugging session where I was trying to discover why e2e tests for code that uses @wordpress/data are failing, I found out that we often use the useSelect hook in a suboptimal way. One that either introduces outright bugs, or is slower and causes more React rerenders than it needs to.

In this post I’m describing several best practices that aim to prevent some common issues. Hopefully it’s helpful and gives you a better understanding of one of WordPress core JavaScript libraries.

Always call selector functions inside the callback

Suppose the onboard store has a getSiteTitle() selector. It might be tempting to call it in a React component like this:

function Title() {
  const { getSiteTitle } = useSelect( ( select ) => select( 'onboard' ) );
  return <h1>{ getSiteTitle() }</h1>;
}

But this code is buggy — the component will not be reliably rerendered when the store’s siteTitle state changes and will keep showing the old value.

Why? useSelect doesn’t just read the desired values from the store, which is the obvious and visible part of what it does. It also establishes a subscription to the store and triggers the rerender of the component when the relevant parts of the state change. What does “relevant” mean here? The precise condition is: when the new return value of useSelect is not shallowly equal to the previous one.

In our case, the return value is the { getSiteTitle } object and the getSiteTitle property value never changes. It’s still the same function. It’s only the return value of that function that changes, but that’s not what we are checking. The return value is always shallowly equal to the previous one.

The Title component will be rerendered with the new siteTitle value only when the rerender is triggered by something else. Maybe the component has some internal state that changes, a prop changes, or the rerender is triggered merely by a parent component rerendering.

The fixed component looks like this:

function Title() {
  const { siteTitle } = useSelect( ( select ) => ( {
    siteTitle: select( 'onboard' ).getSiteTitle()
  } ) );
  return <h1>{ siteTitle }</h1>;
}

Here useSelect triggers a rerender whenever the old siteTitle and new siteTitle are different, just as we’d expect.

This pattern where useSelect returns selector functions instead of their return values is surprisingly common in both Calypso and Gutenberg codebases.

Select the data you need inside the callback

Now suppose the onboard store maintains value for several fields like siteTitle, siteDesign and siteDomain. And it provides a getState() selector that returns an object will all these fields together. Then you might get the siteTitle value like this:

function Title() {
  const { siteTitle } = useSelect( ( select ) => select( 'onboard' ).getState() );
  return <h1>{ siteTitle }</h1>;
}

This code behaves correctly and doesn’t cause bugs with missed updates, like the getSiteTitle example, but its performance is suboptimal. Rerenders will be triggered too often.

Although the component is interested only in the siteTitle value, useSelect doesn’t know that. It’s asked to return the entire getState(), including the siteDesign and siteDomain values. And it will trigger a rerender whenever any of them changes, even when siteTitle remains the same.

A more performant version would be:

function Title() {
  const { siteTitle } = useSelect( ( select ) => {
    const state = select( 'onboard' ).getState();
    return { siteTitle: state.siteTitle };
  } );
  return <h1>{ siteTitle }</h1>;
}

This is not as concise as the slower version, you’ll be fighting with the “siteTitle is already declared in the upper scope” ESLint error… I can see why it can be tempting.

Another variation of the same principle is this component:

function ContinueButton() {
  const { siteTitle } = useSelect( ( select ) => ( {
    siteTitle: select( 'onboard' ).getSiteTitle()
  } );
  return <button disabled={ siteTitle.length === 0 }>Continue</button>;
}

This button will rerender every time siteTitle changes, e.g., as you type into an input field. But most of these rerenders will be wasted, because the disabled prop will remain true. It’s better to calculate the boolean derived value inside the useSelect callback:

function ContinueButton() {
  const { hasTitle } = useSelect( ( select ) => ( {
    hasTitle: select( 'onboard' ).getSiteTitle().length > 0
  } );
  return <button disabled={ ! hasTitle }>Continue</button>;
}

Prefer returning objects with properties from the callback

You could rewrite the Title example into a more concise form:

function Title() {
  const siteTitle = useSelect( ( select ) => select( 'onboard' ).getState().siteTitle );
  return <h1>{ siteTitle }</h1>;
}

Can I return the siteTitle value directly instead of the { siteTitle } object? Is it a good idea?

The answer is that this will almost always work, changes of siteTitle will almost always be correctly detected, but not 100% of the time.

The return value will be compared using @wordpress/is-shallow-equal, and it depends whether that library can compare values of your data type correctly. Consider this counterexample where the library will fail:

const { default: eq } = require( '@wordpress/is-shallow-equal' );

function get() {
    return this.value;
}

function createBox( value ) {
    const rv = { get };
    Object.defineProperty( rv, 'value', { enumerable: false, value } );
    return rv;
}

const one = createBox( 1 );
const two = createBox( 2 );

console.log( `Are ${ one.get() } and ${ two.get() } equal? ${ eq( one, two ) }` );

If you try to run this script in Node.js, you’ll see a surprising result:

Are 1 and 2 equal? true

The value property is semi-private, and Object.keys won’t return it. The shallow compare function will see only the get property which is always the same.

If you store instances of createBox in your data store, useSelect might fail to see changed values.

Another fun way how to implement objects with hidden private properties and trick @wordpress/is-shallow-equal is:

const valueMap = new WeakMap();

function get() {
  return valueMap.get( this );
}

function createBox( value ) {
  const rv = { get };
  valueMap.set( rv, value );
  return rv;
}

This technique is used by Babel to transpile JavaScript private class properties, so expect to see these weakmaps in your transpiled code soon 🙂

You might say that you only use nice objects and strings and numbers in your state and you’d be right. Returning them directly from useSelect will be fine. But shallow-comparing arbitrary objects can become messy and one day it might backfire. On the other hand, returning an object to be destructured is guaranteed to be always safe.

Do all selections from the same store in one callback

Which one of the following is better?

const { siteTitle } = useSelect( ( select ) => ( {
  siteTitle: select( 'onboard' ).getSiteTitle()
} );
const { siteDesign } = useSelect( ( select ) => ( {
  siteDesign: select( 'onboard' ).getSiteDesign()
} );

or

const { siteTitle, siteDesign } = useSelect( ( select ) => {
  const store = select( 'onboard' );
  return {
    siteTitle: store.getSiteTitle(),
    siteDesign: store.getSiteDesign(),
  };
} );

The second one is slightly faster, but not much. Two calls to useSelect will make the component to establish two subscriptions to the data store or registry. On each change, the subscription handler inside useSelect will be called twice, and one of the calls will be redundant and wasted.

What if I select from two different stores? In the current implementation, it will still be faster to merge them into one useSelect, but that might change in the future. If the subscriptions become more granular and store-specific, it could be two separate useSelect calls that will be faster.

The Big Burger Course

Some photos from the burger cooking class I took yesterday at the Prague Culinary Institute. We did four burgers in total and also baked some buns.

Classical cheeseburger from ground beef (please excuse that the actual burger is out of focus 🙃)
Burger with flank steak
Pork burger with an Asian-style salad from cabbage and carrots
Frying the breaded fish burgers
The finished fish burger
Mixing the dough for burger buns
Rolling the buns
The buns are baked, ready for consumption

Hardware Effects

Yesterday I attended C++ meetup regularly organized by Avast folks in their Prague offices. There was a great talk by Jakub Beránek, a talented CS student from Ostrava, about observing performance effects that the internal CPU architecture has on your code.

In this awesomely organized and documented repo, there are C++ benchmark programs that demonstrate thinks like the following:

  • Branch misprediction: if the CPU is able to predict the result of an if condition correctly, the code runs much faster. This is also a topic of the most upvoted Stack Overflow question ever.
  • False sharing: if two threads write to memory locations that are close enough to be in one cache line, the L1/L2/L3 caches need to be flushed all the time and the program slows down a lot. This is one of reasons why immutable data structures can produce faster programs, even if they do more memory allocations and copying.
  • Bandwidth saturation: there is a limit how fast data can be transferred between memory and CPU. Spawning more processes and threads, even if they don’t share memory at all, can eventually saturate the bandwidth and slow your program down.

And there are many more examples like that. Did you know that CPU caches are basically hash tables and that your program can degrade their performance if it manages to generate hash collisions?

The performance nerd in me is so happy.

Optimizing JavaScript code for JITs

Here is an awesome article about optimizing JavaScript code for JIT compilers. Describes techniques on how to extract information on where V8 spends time, and most of them are surprisingly accessible to someone with little optimizing-compiler skills.

Then Shu-yu Guo retweeted it with a link to their paper about Optimization Coaching in devtools. There is an experimental and unfinished feature (hidden under the devtools.performance.ui.show-jit-optimizations pref) in Firefox that implements these ideas.