Wild React πŸ¦’: useForceUpdate()

October 30, 2019

9 min read β˜•οΈβ˜•οΈ

You ain't wild, ya mild. Photo: Β© pixabay @ pexels

You ain't wild, ya mild. Photo: Β© pixabay @ pexels

Y'all wanna see something wild? πŸ¦’πŸ‘€


A little known method

There is a method in the React component class API that is fairly unknown and rarely used: forceUpdate(). As the React docs state,

By default, when your component’s state or props change, your component will re-render. If your render() method depends on some other data, you can tell React that the component needs re-rendering by calling forceUpdate()

In a normal React application, changes to props, state, and even context is the main driver of component render cycles. However, you can tell React to re-render your component without updating any of those things. And one special way to do that is to call forceUpdate().

Let's take a glance at what this may look like:

class BadExample extends React.Component {
  callJGWentworth = () => {
    // call J.G. Wentworth, or just tell React we need it now πŸ‘‡
    this.forceUpdate();
  };
  render() {
    console.log('877-CASH-NOW!!!');

    return (
      <>
        <h2>I have a structured settlement</h2>
        <button type="button" onClick={this.callJGWentworth}>
          AND I NEED CASH NOW
        </button>
      </>
    );
  }
}
React component forceUpdate() example usage

Yeah, I know the code is bad, but it's only a reflection of my daily code production an example. It actually doesn't do anything. kinda

Be a lot cooler if it did

Be a lot cooler if it did

Here's what running the above code looks like:

I have a structured settlement


🎢 877-CASH-NOW!!! 🎢

One thing to be aware of is that forceUpdate() will skip shouldComponentUpdate() (which by default just returns true).

Going back to the React docs:

Calling forceUpdate() will cause render() to be called on the component, skipping shouldComponentUpdate(). This will trigger the normal lifecycle methods for child components, including the shouldComponentUpdate() method of each child. React will still only update the DOM if the markup changes.

That last sentence simply means that calling forceUpdate() could end up being a no-op function, e.g. () => {}. If nothing in the DOM will change by forcing a re-render, then you've done some wasted work on the UI thread. And we don't want that.

Use forceUpdate() sparingly and with intention.


forceUpdate(), the React hooks hway

If you've used React hooks, you've probably noticed that:

  1. They're awesome 😎
  2. They don't have a hook corollary to componentDidCatch or forceUpdate() (hey, what gives??)

Alright, well maybe you haven't noticed point #2. That's alright, alright, alright.

It may not exist as a first class hook, but that can't stop us! Let's write a custom hook!

function useForceUpdate() {
  const [, forceUpdate] = React.useState();

  return React.useCallback(() => {
    forceUpdate((s) => !s);
  }, []);
}
forceUpdate(), but as a hook: useForceUpdate()

So, what does this hook do?

Well, it first calls React.useState() and passes nothing, or undefined, to the initial state. Notice however, that it doesn't care about the first argument returned in the useState() tuple, which is the state variable. We will soon see that this is okay though, because our initial state is falsey, which is just a funny way of saying that it evaluates to false. It's important to note that we've effectively made the state variable private to the custom hook, or hidden to the outside users. The state variable still exists, but we just don't expose it for use. Here's what happens in React DevTools:

useForceUpdate in React DevTools βš›οΈ

useForceUpdate() in React DevTools βš›οΈ

The last thing that the useForceUpdate hook does is return a memoized callback that simply toggles the unnamed state variable. Just like in the React class component method setState, you can pass an updater function to the React.useState() setter, as I've done here. This updater function just happens to return the inverse of what the unnamed state variable currently is, e.g. false ➑️ true.

If you're unsure about what I mean by "memoized callback", that's okay. In this case, we just want the function () => { forceUpdate(); } to always refer to the same underlying object reference. So, we're caching, or remembering, what the function is, even across separate calls to useForceUpdate. If you're curious for more info on memoization, check out this Wikipedia article.

To answer the question "What does this hook do?", it allows you to force a re-render in your functional component without actually changing props, state, or context.

I have misled you a bit, however. The useForceUpdate() hook isn't quite a 1-to-1 comparison with the forceUpdate() class component usage above. That's because there's no shouldComponentUpdate() hook. Hooks require a different mental model of React.

Example Time! ⏰

Here's the part I've been wanting to show you. But, before I reveal the code we will be running, I want to talk about a couple of things.

First, let's touch back on what causes a React component to re-render. As I mentioned before, a component will re-render if:

  • its shouldComponentUpdate() returns true
  • its own internal state changes
  • it receives new props
  • the context it hooks into changes
  • it's a class component that calls forceUpdate()

Notice that changing React refs don't cause a re-render. Refs are nice for imperatively mutating DOM nodes. I primarily use them for focus management: ref.current.focus(). But, you can also use them to store instance-like-variables, even in functional components! Keep in mind that changing these kinds of instance-like-variables won't trigger a re-render.

Next, I wanna briefly talk about one of my favorite hooks: React.useEffect(). And by briefly talk about, I really just mean referencing a tweet from @ryanflorence.

A quick and helpful guide on React.useEffect():

Ryan Florence on Twitter: "@dan_abramov @_developit @mjackson The question is not "when does this effect run" the question is "with which state does this effect synchronize with" useEffect(fn) // all state useEffect(fn, []) // no state useEffect(fn, [these, states])"

Tweet: Ryan Florence (@ryanflorence) May 5, 2019

Now that that's out of the way, let's see some code, Cody! πŸ€“

function UseTheForce() {
  const forceUpdate = useForceUpdate();
  const renderCount = React.useRef(0);

  React.useEffect(() => {
    renderCount.current += 1;
  });

  const onClick = React.useCallback(() => {
    forceUpdate();
  }, [forceUpdate]);

  return (
    <>
      <button type="button" onClick={onClick}>
        Use the Force πŸ‘‹
      </button>
      <div>Render count: {renderCount.current}</div>
    </>
  );
}

And here it is, running (((wild))). Go ahead, give it a click. πŸ‘‡

Render count: 0

Well there it is

Well there it is

The oddball UseTheForce component, rendered in all its glory.

How neat is that? 🌲

How neat is that? 🌲

Let's break it down πŸ’ƒ

First, we get our forceUpdate() callback:

// get the forceUpdate() callback by calling the useForceUpdate() hook
const forceUpdate = useForceUpdate();
Get the forceUpdate() callback by calling the useForceUpdate() hook

Now, let's grab a ref to a pseudo-instance-variable counter, initialized to 0:

// let's retain some counting state without useState()... with refs!
// initialize the ref.current value to 0
const renderCount = React.useRef(0);
Use state without useState(), ya dig?

Using our handy dandy notebook πŸ“• React.useEffect() guide from Ryan Florence, let's increment the counter on each render:

// add one to the render count ref's current value on each render
React.useEffect(() => {
  renderCount.current += 1;
}); // don't pass any dependency array here, not even empty list []
Add one to the render count ref's current value on each render

A simple click handler is necessary for the <button>:

// When I click, you... don't click, just force the update!
const onClick = React.useCallback(() => {
  forceUpdate();
}, [forceUpdate]);
Force an update when we click the button

And finally, draw the rest of the ****ing owl the markup:

// render the button and our "state variable" render count
return (
  <>
    <button type="button" onClick={onClick}>
      Use the Force πŸ‘‹
    </button>
    <div>Render count: {renderCount.current}</div>
  </>
);
The example markup

Conclusion

TLDR: Don't do this.*

This was definitely an exercise in what if? territory. While there are certainly use cases for forceUpdate(), odds are you're better off using hooks, props, state, and context to declaratively render what you want and when.

If nothing in the DOM will change by forcing a re-render, then you've done some wasted work on the UI thread. The UI thread is a precious resource and we should be using it as little as possible for maximum performance.

Again, use forceUpdate() sparingly and with intention.

And if you're thinking about reaching for something like the custom useForceUpdate() hook above, then you're probably holding it wrong.

Thanks for coming to my TED talk. After all, all the cool kids are doing it.

\*Unless you have good reason to. Terms and conditions Nuance applies.

Comments