In legacy roots, if an update originates outside of `batchedUpdates`,
check if it's inside an `act` scope; if so, treat it as if it were
batched. This is only necessary in legacy roots because in concurrent
roots, updates are batched by default.
With this change, the Test Utils and Test Renderer versions of `act` are
nothing more than aliases of the isomorphic API (still not exposed, but
will likely be the recommended API that replaces the others).
This API is only used by the event system, to set the event priority for
the scope of a function. We don't need it anymore because we can modify
the priority directly, like we already do for continuous input events.
Forgot to stage this before committing 54e88ed12
I don't think is currently observable but should include the guard to
protect against regressions (though this whole block will be deleted
along with legacy mode, anyway).
* Re-land recent flushSync changes
Adds back #21776 and #21775, which were removed due to an internal
e2e test failure.
Will attempt to fix in subsequent commits.
* Failing test: Legacy mode sync passive effects
In concurrent roots, if a render is synchronous, we flush its passive
effects synchronously. In legacy roots, we don't do this because all
updates are synchronous — so we need to flush at the beginning of the
next event. This is how `discreteUpdates` worked.
* Flush legacy passive effects at beginning of event
Fixes test added in previous commit.
Made several changes to the hooks name cache to avoid losing cached data between selected elements:
1. No longer use React-managed cache. This had the unfortunate side effect of the inspected element cache also clearing the hook names cache. For now, instead, a module-level WeakMap cache is used. This isn't great but we can revisit it later.
2. Hooks are no longer the cache keys (since hook objects get recreated between element inspections). Instead a hook key string made of fileName + line number + column number is used.
3. If hook names have already been loaded for a component, skip showing the load button and just show the hook names by default when selecting the component.
* Add named hooks test case built with Rollup
* Fix prepareStackTrace unpatching, remove sourceURL
* Prettier
* Resolve source map URL/path relative to the script
* Add failing tests for multi-module bundle
* Parse hook names from multiple modules in a bundle
* Create a HookSourceData per location key (file, line, column).
* Cache the source map per runtime URL ( = file part of location key).
* Don't store sourceMapContents - only store a consumer instance.
* Look up original source URLs in the source map correctly.
* Cache the code + AST per original URL.
* Fix off-by-one column number lookup.
* Some naming and typing tweaks related to the above.
* Stop storing the consumer outside the with() callback, which is a bug.
* Lint fix for 8d8dd25
* Added devDependencies to react-devtools-extensions package.json
* Added some debug logging and TODO comments
* Added additional DEBUG logging to hook names cache
Co-authored-by: Brian Vaughn <bvaughn@fb.com>
There's a weird quirk leftover from the old Stack (pre-Fiber)
implementation where the initial mount of a leagcy (ReactDOM.render)
root is flushed synchronously even inside `batchedUpdates`.
The original workaround for this was an internal method called
`unbatchedUpdates`. We've since added another API that works almost the
same way, `flushSync`.
The only difference is that `unbatchedUpdates` would not cause other
pending updates to flush too, only the newly mounted root. `flushSync`
flushes all pending sync work across all roots. This was to preserve
the exact behavior of the Stack implementation.
But since it's close enough, let's just use `flushSync`. It's unlikely
anyone's app accidentally relies on this subtle difference, and the
legacy API is deprecated in 18, anyway.
* Replace flushDiscreteUpdates with flushSync
flushDiscreteUpdates is almost the same as flushSync. It forces passive
effects to fire, because of an outdated heuristic, which isn't ideal but
not that important.
Besides that, the only remaining difference between flushDiscreteUpdates
and flushSync is that flushDiscreteUpdates does not warn if you call it
from inside an effect/lifecycle. This is because it might get triggered
by a nested event dispatch, like `el.focus()`.
So I added a new method, flushSyncWithWarningIfAlreadyRendering, which
is used for the public flushSync API. It includes the warning. And I
removed the warning from flushSync, so the event system can call that
one. In production, flushSyncWithWarningIfAlreadyRendering gets inlined
to flushSync, so the behavior is identical.
Another way of thinking about this PR is that I renamed flushSync to
flushSyncWithWarningIfAlreadyRendering and flushDiscreteUpdates to
flushSync (and fixed the passive effects thing). The point is to prevent
these from subtly diverging in the future.
* Invert so the one with the warning is the default one
To make Seb happy
Now that discrete updates are flushed synchronously in a microtask,
the `discreteUpdates` method used by our event system is only a
optimization to save us from having to check `window.event.type` on
every update. So we should be able to remove the extra logic.
Assuming this lands successfully, we can remove `batchedEventUpdates`
and probably inline `discreteUpdates` into the renderer, like we do
for continuous updates.
When migrating some internal tests I found it annoying that I couldn't
return anything from the `act` scope. You would have to declare the
variable on the outside then assign to it. But this doesn't play well
with type systems — when you use the variable, you have to check
the type.
Before:
```js
let renderer;
act(() => {
renderer = ReactTestRenderer.create(<App />);
})
// Type system can't tell that renderer is never undefined
renderer?.root.findByType(Component);
```
After:
```js
const renderer = await act(() => {
return ReactTestRenderer.create(<App />);
})
renderer.root.findByType(Component);
```
* Move error logging to update callback
This prevents double logging for gDSFE boundaries with createRoot.
* Add an explanation for the rest of duplicates
* Enable skipped tests from #21723
* Report uncaught errors in DEV
* Clear caught error
This is not necessary (as proven by tests) because next invokeGuardedCallback clears it anyway. But I'll keep it for consistency with other calls.
When wrapping an update in act, instead of scheduling a microtask,
we can add the task to our internal queue.
The benefit is that the user doesn't have to await the act call. We can
flush the work synchronously. This doesn't account for microtasks that
are scheduled in userspace, of course, but it at least covers
React's usage.