Strict Mode
In React, there are lots of subtle gotchas, things that can cause significant problems for our users, but that aren't necessarily obvious to us as developers.
To help us find and fix these issues, the React team has introduced a new “Strict Mode”.
To enable “Strict Mode”, we wrap our application in a React.StrictMode
element:
import React from 'react';
root.render( <React.StrictMode> <App /> </React.StrictMode>);
This element flips a switch inside React, adding in a bunch of restrictions and safeguards designed to improve the robustness of our application.
One of these tweaks in particular is pretty controversial, and has led to a lot of confusion.
Let's talk about it.
Video Summary
We're looking at the previous example, adding the spacebar keyboard shortcut to our media player.
Let's suppose we remove the cleanup function:
React.useEffect(() => { function handleKeyDown(event) { if (event.code === 'Space') { setIsPlaying((currentIsPlaying) => !currentIsPlaying); } }
window.addEventListener('keydown', handleKeyDown);
return () => { // Removed the cleanup: // window.removeEventListener('keydown', handleKeyDown); }; }, []);
As we've discussed, this introduces a memory leak:
- Every time this component is mounted, we add another event listener, stacking them up like hotcakes
- Because the
handleKeyDown
callback references a part of the component instance —setIsPlaying
— the JS garbage collector can't clean up the instances.
Here's the thing, though: these problems are super subtle, in that they only show up in long-running processes / on machines with really limited memory. We likely won't notice any performance problems as we test, even if we unmount/remount the component a few times.
And there's no other clues, either; no lint warnings, no console errors.
Our first clue that something is wrong will likely come days or weeks after we deploy this code. Our support team will start seeing tickets like "Why is this media player using 90% of my computer's memory?", or "Why is this media player causing my fans to spin up like jet engines??".
Let me tell you, as someone who has been on support rotation and had to hunt down bugs like this: it's no fun at all.
To help us spot these sorts of issues in development, the React team has introduced Strict Mode.
To enable Strict Mode, we wrap the React.StrictMode
element around our application. This flips a switch inside React.
With Strict Mode enabled, this subtle problem has turned into a serious one: the spacebar key shortcut doesn't appear to work at all!
To explain what's going on here: Strict Mode automatically reruns all effects. And so, the following code is executed twice, not once:
function handleKeyDown(event) { if (event.code === 'Space') { setIsPlaying((currentIsPlaying) => !currentIsPlaying); }}
window.addEventListener('keydown', handleKeyDown);
As a result, we register two event listeners, both connected to the same component instance. When we hit the "Space" key, we call setIsPlaying(!false)
, setting it to true
... but then we immediately call it again, setting it back to false
.
To fix this problem, we can re-implement our cleanup function:
React.useEffect(() => { function handleKeyDown(event) { if (event.code === 'Space') { setIsPlaying((currentIsPlaying) => !currentIsPlaying); } }
window.addEventListener('keydown', handleKeyDown);
return () => { window.removeEventListener('keydown', handleKeyDown); }; }, []);
In "Normal" mode, here's the sequence of operations:
- Mount
- Run effect
In Strict Mode, the sequence is:
- Mount
- Run effect
- Run cleanup
- Re-run effect
The React team added this behaviour specifically to help surface subtle issues like this. Without a cleanup function, lots of things will break if we try and run them twice!
You might be thinking: isn't it expensive to run all effects twice? Won't this affect performance?
And the answer is yes, but with an asterisk.
Strict Mode only affects the development environment. It has no effect in production. This means that when we bundle and deploy our applications, Strict Mode is a no-op. It doesn't do anything.
So far in this course, we have not been using Strict Mode, because it does make things a bit more complicated, and useEffect
is already complicated enough when we're first learning it. But now that we've covered the basics, I think it's worth flipping the switch.
It can definitely make some situations more challenging, but the thing to remember is that Strict Mode doesn't cause problems, it surfaces problems. Strict Mode turns subtle bugs into glaringly-obvious ones. In the long run, Strict Mode should make our life easier, not harder.
Here's the sandbox from the video above:
Code Playground
More information about Strict Mode
In addition to re-running all effects, Strict Mode changes a number of other things. We can group these changes into 2 categories:
- Safeguards designed to amplify potential issues
- Warnings about deprecated APIs
Unless you find yourself working in a legacy codebase (at least 5 years old), you're not likely to run into any of the deprecation warnings.
But you are likely to notice some of the safeguards, like the one we saw above about duplicated effects. At this time of writing, all of these safeguards follow the same pattern: running things twice.
For example, in Strict Mode, each render is automatically re-run. If we take another look at the Digital Clock example with Strict Mode enabled, we notice that each re-render happens twice:
Code Playground
- Clock re-render!
- Clock re-render!
- Clock re-render!
- Clock re-render!
- Clock re-render!
- Clock re-render!
- Clock re-render!
- Clock re-render!
- Clock re-render!
- Clock re-render!
- Clock re-render!
- Clock re-render!
If you're curious, you can see a full list of Strict Mode changes by reading the official documentation.
The new default
While Strict Mode is technically an optional, “opt in” mode, it's quickly become the standard way to use React. Just about every boilerplate and meta-framework will use Strict Mode by default!
I want to make sure you're learning "real-world" React, and most real-world projects use Strict Mode. And so, from this moment onwards, Strict Mode will be enabled by default on this course platform.
I've added a little toggle to the sandbox which will show you whether Strict Mode is enabled, and allow you to toggle it on:
Try it out for yourself here! You should see two “Mount check!” console logs when Strict Mode is enabled, and only one when it's disabled:
Code Playground
- Mount check!
- Mount check!
In general, I recommend leaving Strict Mode on. The toggle is provided for troubleshooting purposes.
Simulating Strict Mode
As I was developing this lesson, a thought occurred to me: is it possible to “simulate” this Strict Mode quirk, by unmounting/remounting the component?
Well, let's explore:
Video Summary
In Strict Mode, the following sequence occurs:
- Effect
- Cleanup (if provided)
- Effect
What if we disable Strict Mode, and instead, we unmount/remount the component? This would lead to the exact same order of operations:
- Effect
- Cleanup (if provided)
- Effect
Well, if we try it out, we discover that no, it is not the same thing. The keyboard shortcut still works if we follow this approach.
But what's different?
I'll be honest, I didn't know. Fortunately, a kind member of the React core team was able to fill me in.
Here's the difference: Strict Mode doesn't actually unmount/remount the component. This means that there's a single component instance, and we're calling the effect function twice.
When we unmount and remount the component, we create a brand-new component instance, with its own isPlaying
internal state.
Check out the diagrams below, they illustrate the difference.
Another way to explain this: Suppose we click the "Toggle media player" button once, to unmount it. We've run the effect + the cleanup function. Our event listener lives on, but the spacebar key does nothing; this is because there is no more MediaPlayer
. We're toggling the state on a component that isn't active anymore. There is no <audio>
tag to be playing!
When we click the "Toggle media player" button again, we mount a fresh instance of the MediaPlayer
component, with a fresh isPlaying
state variable, and we create a fresh event listener. The old event listener is still there, but it's effectively a no-op. And so, things still work properly (though we have introduced a memory leak).
Here are the illustrations from the video:
Strict Mode:
Strict Mode (simulated):