October 30, 2019 β’ βοΈ 4 min read
Updated: 12/12/2019, 12:45:39 AM
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 callingforceUpdate()
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>
</>
)
}
}
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
Here's what running the above code looks like:
I have a structured settlement
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 causerender()
to be called on the component, skippingshouldComponentUpdate()
. This will trigger the normal lifecycle methods for child components, including theshouldComponentUpdate()
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:
- They're awesome π
- 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)
}, [])
}
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:
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()
returnstrue
- 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()
:
The question is not "when does this effect run" the question is "with which state does this effect synchronize with"
β Ryan Florence (@ryanflorence) May 5, 2019
useEffect(fn) // all state
useEffect(fn, []) // no state
useEffect(fn, [these, states])
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. π
The oddball UseTheForce
component, rendered in all its glory.
Let's break it down π
First, we get our forceUpdate()
callback:
// get the forceUpdate() callback by calling the useForceUpdate() hook
const forceUpdate = useForceUpdate()
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)
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 []
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])
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>
</>
)
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.