* Restrict effect return type to a function or nothing
We already warn in dev if the wrong type is returned. This updates the
Flow type.
* Restrict return type further
* Assume Effect hook returns either a function or undefined
* Tweak warning message
* Improve Reducer Hook's lazy init API
* Use generic type for initilizer input
Still requires an `any` cast in the case where `init` function is
not provided.
* Move DEV-only function right above where it's used
I don't like looking at this top-level function #petty
* Use different dispatchers for functions & classes
Classes support readContext, but not any of the other dispatcher
methods. Function support all methods.
This is a more robust version of our previous strategy of checking
whether `currentlyRenderingFiber` is null.
As a next step, we can use a separate dispatcher for each phase of the
render cycle (mount versus update).
* Use separate dispatchers for mount and update
* Remove mount code from update path
Deletes mount-specific code from the update path, since it should be
unreachable. To continue supporting progressive enhancement (mounting
new hooks at the end of the list), we detect when there are no more
current hooks and switch back to the mount dispatcher. Progressive
enhancement isn't officially supported yet, so it will continue to warn.
* Factoring nits
* Fix Flow
Had to cheat more than I would like
* More Flow nits
* Switch back to using a special dispatcher for nested hooks in DEV
In order for this strategy to work, I had to revert progressive
enhancement support (appending hooks to the end). It was previously a
warning but now it results in an error. We'll reconsider later.
* Always pass args to updateState and updateReducer
Even though the extra args are only used on mount, to ensure
type consistency.
* Turned enableHooks feature flag on everywhere
* Removed useHooks feature flag from tests (now that it's on by default)
* Remove useHooks feature flag entirely
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.
* react-debug-tools accepts currentDispatcher ref as param
* ReactDebugHooks injected dispatcher ref is optional
* Support custom values for custom hooks
* PR feedback:
1. Renamed useDebugValueLabel hook to useDebugValue
2. Wrapped useDebugValue internals in if-DEV so that it could be removed from production builds.
* PR feedback:
1. Fixed some minor typos
2. Added inline comment explaining the purpose of rollupDebugValues()
3. Refactored rollupDebugValues() to use a for loop rather than filter()
4. Improve check for useDebugValue hook to lessen the chance of a false positive
5. Added optional formatter function param to useDebugValue
* Nitpick renamed a method
I don't think "array or iterator" is adding anything, and it may well be confusing, especially since this is one of the first and most common warnings that devs see.
* Memoize promise listeners to prevent exponential growth
Previously, React would attach a new listener every time a promise is
thrown, regardless of whether the same listener was already attached
during a previous render. Because React attempts to render every time
a promise resolves, the number of listeners grows quickly.
This was especially bad in synchronous mode because the renders that
happen when the promise pings are not batched together. So if a single
promise has multiple listeners for the same root, there will be multiple
renders, which in turn results in more listeners being added to the
remaining unresolved promises. This results in exponential growth in
the number of listeners with respect to the number of IO-bound
components in a single render.
Fixes#14220
* Memoize on the root and Suspense fiber instead of on the promise
* Add TODO to fix persistent mode tests
* Add a test for current defaultProps behavior in lazy
* Add a warning against definining defaultProps on the outer wrapper
* Warn about setting propTypes too
* Remove redundant async
* Validate propTypes for resolved lazy types
Note this only works for elements created after resolving. So it's not ideal. But it provides the best stack trace for those cases.
* Add a test for lazy(forwardRef()) propTypes check
* Validate memo() inner propTypes and warn about shadowing
* Add test verifying nested lazy is unsupported
* Change error wording to remove "Promise elements"
* Improve error message for nested lazy() and add tests
* Validate propTypes for memo in the reconciler when necessary
* Add comments for why we're calling checkPropTypes
* Fix Flow and lint
* Undo unintentional formatting changes
* Remove unnecessary case (it is handled by function code path)
* Add test coverage for memo(fn.defaultProps).propTypes
* Test should be agnostic of where resolving happens
That's an implementation detail and we might want to change it later. Let's keep it easy by making tests just check that validation happened, not at which stage.
* Unify traversal logic in createElement
This moves all type traversal into createElement. When lazy resolves, we call createElement once to re-check.
* Match prod behavior for propTypes/defaultProps shims closer
* Revert "Unify traversal logic in createElement"
This reverts commit 2e77ca47fe.
See https://github.com/facebook/react/pull/14298#issuecomment-442687775
* Undo unnecessary change to getComponentName
useMutationEffect has problems (namely, refs aren't attached at the time that it runs) and we're not positive it's necessary. useLayoutEffect runs at the same time as componentDidMount/Update so it's sufficient for all existing use cases; it can be used in any case that useEffect happens too late. Until we figure out what we want to do, let's delete it.
* BUG: ReactPartialRenderer / New Context polutes mutable global state
The new context API stores the provided values on the shared context instance. When used in a synchronous context, this is not an issue. However when used in an concurrent context this can cause a "push provider" from one react render to have an effect on an unrelated concurrent react render.
I've encountered this bug in production when using renderToNodeStream, which asks ReactPartialRenderer for bytes up to a high water mark before yielding. If two Node Streams are created and read from in parallel, the state of one can polute the other.
I wrote a failing test to illustrate the conditions under which this happens.
I'm also concerned that the experimental concurrent/async React rendering on the client could suffer from the same issue.
* Use unique thread ID for each partial render to access Context
This first adds an allocator that keeps track of a unique ThreadID index
for each currently executing partial renderer. IDs are not just growing
but are reused as streams are destroyed.
This ensures that IDs are kept nice and compact.
This lets us use an "array" for each Context object to store the current
values. The look up for these are fast because they're just looking up
an offset in a tightly packed "array".
I don't use an actual Array object to store the values. Instead, I rely
on that VMs (notably V8) treat storage of numeric index property access
as a separate "elements" allocation.
This lets us avoid an extra indirection.
However, we must ensure that these arrays are not holey to preserve this
feature.
To do that I store the _threadCount on each context (effectively it takes
the place of the .length property on an array).
This lets us first validate that the context has enough slots before we
access the slot. If not, we fill in the slots with the default value.
* Avoid double commit by re-rendering immediately and reusing children
To support Suspense outside of concurrent mode, any component that
starts rendering must commit synchronously without being interrupted.
This means normal path, where we unwind the stack and try again from the
nearest Suspense boundary, won't work.
We used to have a special case where we commit the suspended tree in an
incomplete state. Then, in a subsequent commit, we re-render using the
fallback.
The first part — committing an incomplete tree — hasn't changed with
this PR. But I've changed the second part — now we render the fallback
children immediately, within the same commit.
* Add a failing test for remounting fallback in sync mode
* Add failing test for stuck Suspense fallback
* Toggle visibility of Suspense children in mutation phase, not layout
If parent reads visibility of children in a lifecycle, they should have
already updated.
* [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
Effects scheduled by useEffect should not fire until after the browser
has had a chance to paint. However, they should be fired before any
subsequent mutations.
Also adds useMutationEffect and useLayoutEffect. useMutationEffect fires
during the host update phase. useLayoutEffect fires during the post-
update phase (the same phase as componentDidMount
and componentDidUpdate).
* [react-cache] Remove `cache` as argument to `read`
Updated is API is `Resource.read(key)` instead of
`Resource.read(cache, key)`.
The cache is read from context using `readContext`.
This also removes cache invalidation entirely (other than the default
LRU mechanism), as well as the ability to have multiple caches. We'll
add it back once `Context.write` lands and we can implement it the
right way.
Since there's now only a single cache (the global one), we don't
actually need to use context yet, but I've added a dummy context
anyway so the user gets an error if they attempt to read outside the
render phase.
* nits
* Add test for thenables that resolve multiple times
We are using it with lazy and the combination Suspense + lazy seems pretty
stable. maxDuration is not but that's only enabled when you're in
ConcurrentMode which is still unstable.
* Allow arbitrary types to be wrapped in pure
This creates an outer fiber that container the pure check and an inner
fiber that represents which ever type of component.
* Add optimized fast path for simple pure function components
Special cased when there are no defaultProps and it's a simple function
component instead of class. This doesn't require an extra fiber.
We could make it so that this also works with custom comparer but that
means we have to go through one extra indirection to get to it.
Maybe it's worth it, donno.
Removes support for using arbitrary promises as the type of a React
element. Instead, promises must be wrapped in React.lazy. This gives us
flexibility later if we need to change the protocol.
The reason is that promises do not provide a way to call their
constructor multiple times. For example:
const promiseForA = new Promise(resolve => {
fetchA(a => resolve(a));
});
Given a reference to `promiseForA`, there's no way to call `fetchA`
again. Calling `then` on the promise doesn't run the constructor again;
it only attaches another listener.
In the future we will likely introduce an API like `React.eager` that
is similar to `lazy` but eagerly calls the constructor. That gives us
the ability to call the constructor multiple times. E.g. to increase
the priority, or to retry if the first operation failed.
* 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