* expose unstable_interact for batching actions in tests
* move to TestUtils
* move it all into testutils
* s/interact/act
* warn when calling hook-like setState outside batching mode
* pass tests
* merge-temp
* move jsdom test to callsite
* mark failing tests
* pass most tests (except one)
* augh IE
* pass fuzz tests
* better warning, expose the right batchedUpdates on TestRenderer for www
* move it into hooks, test for dom
* expose a flag on the host config, move stuff around
* rename, pass flow
* pass flow... again
* tweak .act() type
* enable for all jest environments/renderers; pass (most) tests.
* pass all tests
* expose just the warning from the scheduler
* don't return values
* a bunch of changes.
can't return values from .act
don't try to await .act calls
pass tests
* fixes and nits
* "fire events that udpates state"
* nit
* 🙄
* my bad
* hi andrew
(prettier fix)
* Avoid importing Scheduler directly
The reconciler should not depend directly on Scheduler. This adds it to
the host config for the renderer instead.
(Except for `scheduler/tracing` imports, which are used only by the
profiling build. I've left those imports as-is, though I'm open to
directing those through the host config, too.)
* Make throwaway root id longer to appease Brian
Specifying the directory as part of the `repository` field in a `package.json`
allows third party tools to provide better support when working with monorepos.
For example, it allows them to correctly construct a commit diff for a specific
package.
This format was accepted by npm in https://github.com/npm/rfcs/pull/19.
* [Fizz] Add Flow/Jest/Rollup build infra
Add a new package for react-stream which allows for custom server renderer
outputs. I picked the name because it's a reasonable name but also
because the npm name is currently owned by a friend of the project.
The react-dom build has its own inlined server renderer under the
name `react-dom/fizz`.
There is also a noop renderer to be used for testing. At some point
we might add a public one to test-renderer but for now I don't want to have
to think about public API design for the tests.
* Add FormatConfig too
We need to separate the format (DOM, React Native, etc) from the host
running the server (Node, Browser, etc).
* Basic wiring between Node, Noop and DOM configs
The Node DOM API is pipeToNodeStream which accepts a writable stream.
* Merge host and format config in dynamic react-stream entry point
Simpler API this way but also avoids having to fork the wrapper config.
Fixes noop builds.
* Add setImmediate/Buffer globals to lint config
Used by the server renderer
* Properly include fizz.node.js
Also use forwarding to it from fizz.js in builds so that tests covers
this.
* Make react-stream private since we're not ready to publish
or even name it yet
* Rename Renderer -> Streamer
* Prefix react-dom/fizz with react-dom/unstable-fizz
* Add Fizz Browser host config
This lets Fizz render to WHATWG streams. E.g. for rendering in a
Service Worker.
I added react-dom/unstable-fizz.browser as the entry point for this.
Since we now have two configurations of DOM. I had to add another
inlinedHostConfigs configuration called `dom-browser`. The reconciler
treats this configuration the same as `dom`. For stream it checks
against the ReactFizzHostConfigBrowser instead of the Node one.
* Add Fizz Browser Fixture
This is for testing server rendering - on the client.
* Lower version number to detach it from react-reconciler version
Whenever we do this, Rollup needs to materialize this as an object.
This causes it to also add the Babel compatibility property which is
unnecessary bloat. However, since when we use these, we leak the object
this often also deopts any compiler optimizations.
If we really need an object we should export default an object.
Currently there is an exception for DOMTopLevelEventTypes since
listing out the imports is a PITA and it doesn't escape so it should
get properly inlined. We should probably move to a different pattern
to avoid this for consistency though.
* [scheduler] Deadline object -> shouldYield
Instead of using a requestIdleCallback-style deadline object, expose a
method Scheduler.shouldYield that returns true if there's a higher
priority event in the queue.
* Nits
* Store the start time on `updateQueue` instead of `stateNode`
Originally I did this to free the `stateNode` field to store a second
set of children. I don't we'll need this anymore, since we use fragment
fibers instead. But I still think using `updateQueue` makes more sense
so I'll leave this in.
* Use fragment fibers to keep the primary and fallback children separate
If the children timeout, we switch to showing the fallback children in
place of the "primary" children. However, we don't want to delete the
primary children because then their state will be lost (both the React
state and the host state, e.g. uncontrolled form inputs). Instead we
keep them mounted and hide them. Both the fallback children AND the
primary children are rendered at the same time. Once the primary
children are un-suspended, we can delete the fallback children — don't
need to preserve their state.
The two sets of children are siblings in the host environment, but
semantically, for purposes of reconciliation, they are two separate
sets. So we store them using two fragment fibers.
However, we want to avoid allocating extra fibers for every placeholder.
They're only necessary when the children time out, because that's the
only time when both sets are mounted.
So, the extra fragment fibers are only used if the children time out.
Otherwise, we render the primary children directly. This requires some
custom reconciliation logic to preserve the state of the primary
children. It's essentially a very basic form of re-parenting.
* Use `memoizedState` to store various pieces of SuspenseComponent's state
SuspenseComponent has three pieces of state:
- alreadyCaptured: Whether a component in the child subtree already
suspended. If true, subsequent suspends should bubble up to the
next boundary.
- didTimeout: Whether the boundary renders the primary or fallback
children. This is separate from `alreadyCaptured` because outside of
strict mode, when a boundary times out, the first commit renders the
primary children in an incomplete state, then performs a second commit
to switch the fallback. In that first commit, `alreadyCaptured` is
false and `didTimeout` is true.
- timedOutAt: The time at which the boundary timed out. This is separate
from `didTimeout` because it's not set unless the boundary
actually commits.
These were previously spread across several fields.
This happens to make the non-strict case a bit less hacky; the logic for
that special case is now mostly localized to the UnwindWork module.
* Hide timed-out Suspense children
When a subtree takes too long to load, we swap its contents out for
a fallback to unblock the rest of the tree. Because we don't want
to lose the state of the timed out view, we shouldn't actually delete
the nodes from the tree. Instead, we'll keep them mounted and hide
them visually. When the subtree is unblocked, we un-hide it, having
preserved the existing state.
Adds additional host config methods. For mutation mode:
- hideInstance
- hideTextInstance
- unhideInstance
- unhideTextInstance
For persistent mode:
- cloneHiddenInstance
- cloneUnhiddenInstance
- createHiddenTextInstance
I've only implemented the new methods in the noop and test renderers.
I'll implement them in the other renderers in subsequent commits.
* Include `hidden` prop in noop renderer's output
This will be used in subsequent commits to test that timed-out children
are properly hidden.
Also adds getChildrenAsJSX() method as an alternative to using
getChildren(). (Ideally all our tests would use test renderer #oneday.)
* Implement hide/unhide host config methods for DOM renderer
For DOM nodes, we hide using `el.style.display = 'none'`.
Text nodes don't have style, so we hide using `text.textContent = ''`.
* Implement hide/unhide host config methods for Art renderer
* Create DOM fixture that tests state preservation of timed out content
* Account for class components that suspend outside concurrent mode
Need to distinguish mount from update. An unfortunate edge case :(
* Fork appendAllChildren between persistent and mutation mode
* Remove redundant check for existence of el.style
* Schedule placement effect on indeterminate components
In non-concurrent mode, indeterminate fibers may commit in an
inconsistent state. But when they update, we should throw out the
old fiber and start fresh. Which means the new fiber needs a
placement effect.
* Pass null instead of current everywhere in mountIndeterminateComponent
* Deprecate findDOMNode in StrictMode
There are two scenarios. One is that we pass a component instance that is
already in strict mode or the node that we find is in strict mode if
an outer component renders into strict mode.
I use a separate method findHostInstanceWithWarning for this so that
a) I can pass the method name (findDOMNode/findNodeHandle).
b) Can ignore this warning in React Native mixins/NativeComponent that use this helper.
I don't want to expose the fiber to the renderers themselves.
* Add regression test for persistent bailout bug
* Fork more logic into updateHostComponent
This is mostly copy paste. But I added a bailout only to mutation mode. Persistent mode doesn't have that props equality bailout anymore, so the Fabric test now passes.
* Add failing test for persistence host minimalism
* Add bailouts to the persistent host updates
* Add a regression test for 12643#issuecomment-413727104
* Don't diff memoized host components
* Add regression tests for noop renderer
* No early return
* Strengthen the test for host siblings
* Flow types
* Store list of contexts on the fiber
Currently, context can only be read by a special type of component,
ContextConsumer. We want to add support to all fibers, including
classes and functional components.
Each fiber may read from one or more contexts. To enable quick, mono-
morphic access of this list, we'll store them on a fiber property.
* Context.unstable_read
unstable_read can be called anywhere within the render phase. That
includes the render method, getDerivedStateFromProps, constructors,
functional components, and context consumer render props.
If it's called outside the render phase, an error is thrown.
* Remove vestigial context cursor
Wasn't being used.
* Split fiber.expirationTime into two separate fields
Currently, the `expirationTime` field represents the pending work of
both the fiber itself — including new props, state, and context — and of
any updates in that fiber's subtree.
This commit adds a second field called `childExpirationTime`. Now
`expirationTime` only represents the pending work of the fiber itself.
The subtree's pending work is represented by `childExpirationTime`.
The biggest advantage is it requires fewer checks to bailout on already
finished work. For most types of work, if the `expirationTime` does not
match the render expiration time, we can bailout immediately without
any further checks. This won't work for fibers that have
`shouldComponentUpdate` semantics (class components), for which we still
need to check for props and state changes explicitly.
* Performance nits
Optimize `readContext` for most common case
* Fix Portal unmount
Before that change, currentParent is not set as a container even if it should so it break on react-native and probably other custom renderers
* Assert that *ToContainer() methods receive containers
* Add regression tests
* Add comments
* Reset ReactProfilerTimer's DEV-only Fiber stack after an error
* Added ReactNoop functionality to error during "complete" phase
* Added failing profiler stack unwinding test
* Potential fix for unwinding time bug
* Renamed test
* Don't record time until complete phase succeeds. Simplifies unwinding.
* Expanded ReactProfilerDevToolsIntegration-test coverage a bit
* Added unstable_flushWithoutCommitting method to noop renderer
* Added failing multi-root/batch test to ReactProfiler-test
* Beefed up tests a bit and added some TODOs
* Profiler timer differentiates between batched commits and in-progress async work
This was a two-part change:
1) Don't count time spent working on a batched commit against yielded async work.
2) Don't assert an empty stack after processing a batched commit (because there may be yielded async work)
This is kind of a hacky solution, and may have problems that I haven't thought of yet. I need to commit this so I can mentally clock out for a bit without worrying about it. I will think about it more when I'm back from PTO. In the meanwhile, input is welcome.
* Removed TODO
* Replaced FiberRoot map with boolean
* Removed unnecessary whitespace edit
We can support components that suspend outside of an async mode tree
by immediately committing their placeholders.
In strict mode, the Timeout acts effectively like an error boundary.
Within a single render pass, we unwind to the nearest Timeout and
re-render the placeholder view.
Outside of strict mode, it's not safe to unwind and re-render the
siblings without committing. (Technically, this is true of error
boundaries, too, though probably not a huge deal, since we don't support
using error boundaries for control flow (yet, at least)). We need to be
clever. What we do is pretend the suspended component rendered null.*
There's no unwinding. The siblings commit like normal.
Then, in the commit phase, schedule an update on the Timeout to
synchronously re-render the placeholder. Although this requires an extra
commit, it will not be observable. And because the siblings were not
blocked from committing, they don't have to be strict mode compatible.
Another caveat is that if a component suspends during an async render,
but it's captured by a non-async Timeout, we need to revert to sync
mode. In other words, if any non-async component renders, the entire
tree must complete and commit without yielding.
* The downside of rendering null is that the existing children will be
deleted. We should hide them instead. I'll work on this in a follow-up.
* Prepare placeholders before timing out
While a tree is suspended, prepare for the timeout by pre-rendering the
placeholder state.
This simplifies the implementation a bit because every render now
results in a completed tree.
* Suspend inside an already timed out Placeholder
A component should be able to suspend inside an already timed out
placeholder. The time at which the placeholder committed is used as
the start time for a subsequent suspend.
So, if a placeholder times out after 3 seconds, and an inner
placeholder has a threshold of 2 seconds, the inner placeholder will
not time out until 5 seconds total have elapsed.
Expiration times are computed by adding to the current time (the start
time). However, if two updates are scheduled within the same event, we
should treat their start times as simultaneous, even if the actual clock
time has advanced between the first and second call.
In other words, because expiration times determine how updates are
batched, we want all updates of like priority that occur within the same
event to receive the same expiration time. Otherwise we get tearing.
We keep track of two separate times: the current "renderer" time and the
current "scheduler" time. The renderer time can be updated whenever; it
only exists to minimize the calls performance.now.
But the scheduler time can only be updated if there's no pending work,
or if we know for certain that we're not in the middle of an event.
* Inline fbjs/lib/emptyObject
* Explicit naming
* Compare to undefined
* Another approach for detecting whether we can mutate
Each renderer would have its own local LegacyRefsObject function.
While in general we don't want `instanceof`, here it lets us do a simple check: did *we* create the refs object?
Then we can mutate it.
If the check didn't pass, either we're attaching ref for the first time (so we know to use the constructor),
or (unlikely) we're attaching a ref to a component owned by another renderer. In this case, to avoid "losing"
refs, we assign them onto the new object. Even in that case it shouldn't "hop" between renderers anymore.
* Clearer naming
* Add test case for strings refs across renderers
* Use a shared empty object for refs by reading it from React
* Remove string refs from ReactART test
It's not currently possible to resetModules() between several renderers
without also resetting the `React` module. However, that leads to losing
the referential identity of the empty ref object, and thus subsequent
checks in the renderers for whether it is pooled fail (and cause assignments
to a frozen object).
This has always been the case, but we used to work around it by shimming
fbjs/lib/emptyObject in tests and preserving its referential identity.
This won't work anymore because we've inlined it. And preserving referential
identity of React itself wouldn't be great because it could be confusing during
testing (although we might want to revisit this in the future by moving its
stateful parts into a separate package).
For now, I'm removing string ref usage from this test because only this is
the only place in our tests where we hit this problem, and it's only
related to string refs, and not just ref mechanism in general.
* Simplify the condition
* Extract base Jest config
This makes it easier to change the source config without affecting the build test config.
* Statically import the host config
This changes react-reconciler to import HostConfig instead of getting it through a function argument.
Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope).
This is still very broken.
* Add scaffolding for importing an inlined renderer
* Fix the build
* ES exports for renderer methods
* ES modules for host configs
* Remove closures from the reconciler
* Check each renderer's config with Flow
* Fix uncovered Flow issue
We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null.
Help Flow.
* Prettier
* Get rid of enable*Reconciler flags
They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence.
This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing.
Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead.
* Prettier again
* Fix Flow config creation issue
* Fix imprecise Flow typing
* Revert accidental changes
* Support concurrent primary and secondary renderers.
As a workaround to support multiple concurrent renderers, we categorize
some renderers as primary and others as secondary. We only expect
there to be two concurrent renderers at most: React Native (primary) and
Fabric (secondary); React DOM (primary) and React ART (secondary).
Secondary renderers store their context values on separate fields.
* Add back concurrent renderer warning
Only warn for two concurrent primary or two concurrent secondary renderers.
* Change "_secondary" suffix to "2"
#EveryBitCounts
* Decouple update queue from Fiber type
The update queue is in need of a refactor. Recent bugfixes (#12528) have
exposed some flaws in how it's modeled. Upcoming features like Suspense
and [redacted] also rely on the update queue in ways that weren't
anticipated in the original design.
Major changes:
- Instead of boolean flags for `isReplace` and `isForceUpdate`, updates
have a `tag` field (like Fiber). This lowers the cost for adding new
types of updates.
- Render phase updates are special cased. Updates scheduled during
the render phase are dropped if the work-in-progress does not commit.
This is used for `getDerivedStateFrom{Props,Catch}`.
- `callbackList` has been replaced with a generic effect list. Aside
from callbacks, this is also used for `componentDidCatch`.
* Remove first class UpdateQueue types and use closures instead
I tried to avoid this at first, since we avoid it everywhere else in the Fiber
codebase, but since updates are not in a hot path, the trade off with file size
seems worth it.
* Store captured errors on a separate part of the update queue
This way they can be reused independently of updates like
getDerivedStateFromProps. This will be important for resuming.
* Revert back to storing hasForceUpdate on the update queue
Instead of using the effect tag. Ideally, this would be part of the
return type of processUpdateQueue.
* Rename UpdateQueue effect type back to Callback
I don't love this name either, but it's less confusing than UpdateQueue
I suppose. Conceptually, this is usually a callback: setState callbacks,
componentDidCatch. The only case that feels a bit weird is Timeouts,
which use this effect to attach a promise listener. I guess that kinda
fits, too.
* Call getDerivedStateFromProps every render, even if props did not change
Rather than enqueue a new setState updater for every props change, we
can skip the update queue entirely and merge the result into state at
the end. This makes more sense, since "receiving props" is not an event
that should be observed. It's still a bit weird, since eventually we do
persist the derived state (in other words, it accumulates).
* Store captured effects on separate list from "own" effects (callbacks)
For resuming, we need the ability to discard the "own" effects while
reusing the captured effects.
* Optimize for class components
Change `process` and `callback` to match the expected payload types
for class components. I had intended for the update queue to be reusable
for both class components and a future React API, but we'll likely have
to fork anyway.
* Only double-invoke render phase lifecycles functions in DEV
* Use global state to track currently processing queue in DEV
* Move findNodeHandle into the renderers and use instantiation
This is just like ReactDOM does it. This also lets us get rid of injection
for findNodeHandle. Instead I move NativeMethodsMixin and ReactNativeComponent
to use instantiation.
* Refactor findHostInstance
The reconciler shouldn't expose the Fiber data structure. We should pass
the component instance to the reconciler, since the reconciler is the
thing that is supposed to be instancemap aware.
* Fix devtools injection
These are based on the ReactNoop renderer, which we use to test React
itself. This gives library authors (Relay, Apollo, Redux, et al.) a way
to test their components for async compatibility.
- Pass `unstable_isAsync` to `TestRenderer.create` to create an async
renderer instance. This causes updates to be lazily flushed.
- `renderer.unstable_yield` tells React to yield execution after the
currently rendering component.
- `renderer.unstable_flushAll` flushes all pending async work, and
returns an array of yielded values.
- `renderer.unstable_flushThrough` receives an array of expected values,
begins rendering, and stops once those values have been yielded. It
returns the array of values that are actually yielded. The user should
assert that they are equal.
Although we've used this pattern successfully in our own tests, I'm not
sure if these are the final APIs we'll make public.