useMutableSource: Use StrictMode double render to detect render phase mutation (#20698)

* Concurrent Mode test for uMS render mutation

Same test as the one added in #20665, but for Concurrent Mode.

* Use double render to detect render phase mutation

PR #20665 added a mechanism to detect when a `useMutableSource` source
is mutated during the render phase. It relies on the fact that we double
invoke components that error during development using
`invokeGuardedCallback`. If the version in the double render doesn't
match the first, that indicates there must have been a mutation during
render.

At first I thought it worked by detecting inside the *other* double
render, the one we do for Strict Mode. It turns out that while it does
warn then, the warning is suppressed, because we suppress all console
methods that occur during the Strict Mode double render. So it's really
the `invokeGuardedCallback` one that makes it work.

Anyway, let's set that aside that issue for a second. I realized during
review that errors that occur during the Strict Mode double render
reveal a useful property: A pure component will never throw during the
double render, because if it were pure, it would have also thrown during
the first render... in which case it wouldn't have double rendered! This
is true of all such errors, not just the one thrown by
`useMutableSource`.

Given this, we can simplify the `useMutableSource` mutation detection
mechanism. Instead of tracking and comparing the source's version, we
can instead check if we're inside a double render when the error is
thrown.

To get around the console suppression issue, I changed the warning to an
error. It errors regardless, in both dev and prod, so it doesn't have
semantic implications.

However, because of the paradox I described above, we arguably
_shouldn't_ throw an error in development, since we know that error
won't happen in production, because prod doesn't double render. (It's
still a tearing bug, but that doesn't mean the component will actually
throw.) I considered that, but that requires a larger conversation about
how to handle errors that we know are only possible in development. I
think we should probably be suppressing *all* errors (with a warning)
that occur during a double render.
This commit is contained in:
Andrew Clark
2021-02-01 12:11:51 -08:00
committed by GitHub
parent f8545f6eb8
commit dc27b5aaae
4 changed files with 115 additions and 49 deletions
+2 -2
View File
@@ -339,7 +339,7 @@
"345": "Root did not complete. This is a bug in React.",
"348": "ensureListeningTo(): received a container that was not an element node. This is likely a bug in React.",
"349": "Expected a work-in-progress root. This is a bug in React. Please file an issue.",
"350": "Cannot read from mutable source during the current render without tearing. This is a bug in React. Please file an issue.",
"350": "Cannot read from mutable source during the current render without tearing. This may be a bug in React. Please file an issue.",
"351": "Unsupported server component type: %s",
"352": "React Lazy Components are not yet supported on the server.",
"353": "A server block should never encode any other slots. This is a bug in React.",
@@ -373,5 +373,5 @@
"382": "This query has received more parameters than the last time the same query was used. Always pass the exact number of parameters that the query needs.",
"383": "This query has received fewer parameters than the last time the same query was used. Always pass the exact number of parameters that the query needs.",
"384": "Refreshing the cache is not supported in Server Components.",
"385": "Cannot read from mutable source during the current render without tearing. This may be a bug in React. Please file an issue."
"385": "A mutable source was mutated while the %s component was rendering. This is not supported. Move any mutations into event handlers or effects."
}