This doesn't deal with the fact that work is usually deferred
so this will return null for first render (except in sync tests).
It also doesn't deal with top levels being fragments etc.
It doesn't deal with the host instance type being a wrapper
around the public instance. This needs to be unified with refs
and findDOMNode better.
However, this does expose that we reactComponentExpect and
ReactTestUtils doesn't work very well with Fiber.
If work has progressed on a state update that gets resumed
because of another state up, then we won't have an new
pendingProps, and we also won't have any memoizedProps because
it got aborted before completing. In that case, we can just
fallback to the current props.
I think that they can't have diverged because the only way they
diverge is if there is new props.
This lets us bail out on state only updates in more cases which
the unit tests reflect.
We currently only filter out "NoWork" in the beginning of this
function. If the NoWork root is after the one with work it will
show up in the second loop. There's probably a more efficient
way of doing this but this works for now.
This showed up in this PR because a new unit test gets unblocked
which ends up with this case.
This refactors the initialization process so that we can share
it with the "module pattern" initialization.
There are a few new interesting scenarios unlocked by this.
E.g. constructor -> componentWillMount -> shouldComponentUpdate
-> componentDidMount when a render is aborted and resumed.
If shouldComponentUpdate returns true then we create a new
instance instead of trying componentWillMount again or
componentWillReceiveProps without being mounted.
Another strange thing is that the "previous props and state"
during componentWillReceiveProps, shouldComponentUpdate and
componentWillUpdate are all the previous render attempt. However,
componentDidMount's previous is the props/state at the previous
commit. That is because the first three can execute multiple
times before a didMount.
I couldn't figure out how to do this yarn alone so I ended up
just manually hacking the yarn to force a downgrade of babylon
to 6.8.0 since otherwise it gets resolved to 6.12.0 which is
broken.
* Use the memoized props/state from the workInProgress
We don't want to use the "current" props/state because if we have
started work, then pause and continue then we'll have the newer
props/state on it already. If it is not a resume, it should be the
same as current.
* Deprioritize setState within a deprioritized tree
Currently we flag the path to a setState as a higher priority
than "offscreen". When we reconcile down this tree we bail out
if it is a hidden node. However, in the case that node is already
completed we don't hit that bail out path. We end up doing the
work immediately which ends up committing that part of the tree
at a higher priority.
This ensures that we don't let a deprioritized subtree get
reconciled at a higher priority.
* Bump idx in unit test
This proves that this number is actually deprioritized.
Refactors the class logic a bit.
I moved scheduleUpdate out into the scheduler since that's where
the scheduling normally happens. I also moved it so that we can
rely on hoisting to resolve the cycle statically.
I moved the updater to a new class component file. I suspect we
will need a bit of space in here since the class initialization
code is quite complex.
The class component dependency is currently fixed in BeginWork
so we can't move complete or commit phase stuff to it. If we need
to, we have to initialize it in the scheduler and pass it to the
other phases.
If we abort work but have some completed, we can bail out if
the shouldComponentUpdate returns true. However, then we have
a tree that is low priority. When we bailout we currently use
the "current" tree in this case. I don't think this is right.
I'm not sure why I did that.
Similarly, when we complete we use the "current" props if we
didn't have pending props to do. However, we should be using
the memoized props of that tree if it is a pending work tree.
Added a unit test that covers these two cases.
This reorganizes the two commit passes so that all host
environment mutations happens before any life-cycles. That means
that the tree is guaranteed to be in a consistent state at that
point so that you can read layout etc.
This also lets us to detach all refs in the same pass so that
when they get invoked with new instances, that happens after it
has been reset.
During the deletion phase we call detachRefs on any deleted refs.
During the insertion/update phase we call attachRef on class
and host components.
Unfortunately, when a ref switches, we are supposed to call all
the unmounts before doing any mounts. This means that we have to
expact the deletion phase to also include updates in case they
need to detach their ref.
These happen in the commit phase *before* the setState callback.
Unfortunately, we've lost the previous state at this point since
we've already mutated the instance. This needs to be fixed
somehow.
While we're deleting nodes, we need to call the unmount
life-cycle. However, there is a special case that we don't want
to keep deleting every host node along the way since deleting the
top node is enough.
We only use the effect list when we reuse our progressed children.
Otherwise it needs to reset to null.
In all other branches, other than bailoutOnLowPriority, we
revisit the children which recreates this list.
We should also not add fibers to their own effect list since it
becomes difficult to perform work on self without touching the
children too. Nothing else does that neither.
Since that means that the root isn't added to an effect list we
need to special case the root.
This shortcut had a bug associated with it. If beginWork on this
child returns null, then we don't call completeWork on that fiber.
Since removing this short cut adds another time check, we have to
add a single unit of time in tests to account for the top level
call now taking one more unit.
This was also the only recursive call in all of fiber so it's nice
to get rid of it to guarantee that a flat stack is possible.
Instead of passing the full list of children every time to
update the host environment, we'll only do inserts/deletes.
We loop over all the placement effects first and then later
we do the rest.
First clear any progressed deletions for any case where we start
over with the "current" set of children.
Once we've performed a new reconciliation we need to add the
deletions to the side-effect list (which we know is empty because
we just emptied it).
For other effects, instead of just adding a fiber to an effect
list we need to mark it with an update. Then after completion
we add it to the the effect list if it had any effects at all.
This means that we lose the opportunity to control if a fiber
gets added before or after its children but that was already
flawed since we want certain side-effects to happen before others
on a global level.
Instead, we'll do multiple passes through the effect list.
When we reconcile children we need to track the deletions that
happen so that we can perf side-effects later as a result. The
data structure is a linked list where the next pointer uses the
nextEffect pointer.
However, we need to store this side-effect list for reuse if we
end up reusing the progressedChild set. That's why I add a
separate first/last pointer into this list so that we can keep it
for later.
When we don't have any previous fibers we can short cut this path
knowing that we will never have any previous child to compare to.
This also ensures that we don't create an empty Map in this case.
We use this to track the index slot that this Fiber had at
reconciliation time. We will use this for two purposes:
1) We use it to quickly determine if a move is needed.
2) We also use it to model a sparce linked list, since we can have
null/false/undefined in our child set and these take up a slot for
the implicit key.
This implements the first step to proper child reconciliation.
It doesn't yet track side-effects like insert/move/delete but has
the main reconciliation algorithm in place.
The goal of this algorithm is to branch early and avoid rechecking those conditions. That leads to some duplications of code.
There are three major branches:
- Reconciling a single child per type.
- Reconciling all children that are in the same slot as before from the beginning.
- Adding remaining children to a temporary Map and reconciling them by scanning the map.
Even when we use the Map strategy we have to scan the linked list to line up "same slot" positions because React, unlike Inferno, can have implicit keys interleaved with explicit keys.
We'll enable updating of text nodes. To be able to test that we
need the text nodes to be mutable. They're currently just strings
in the Noop renderer so this makes them an object instead.
That exposed a bug in ReactFiberCommitWork for text nodes.
This needs to be fixed somehow. The reconciler could know if you
are mounting this continuation into the same spot as before and
then clone it. However, if the continuation moves, this info is
lost. We'd try to unmount it in one place and mount the same fiber
in another place.
Dropped the unnecessary use of findDOMNode.
Dropped unnecessary arrow functions.
Math.random() -> id counter, since this used to be
non-deterministic which is not ideal for unit tests.
getOriginalKeys() used to rely on implementation details
to scan that the internal data structure maintained its
structure, however, since that is unobservable we don't
need to test the internal data structure itself. We're
already testing refs and dom structure which is the only
observable effect of the reorder.
These nodes rendering into Text nodes in the DOM.
There is a special case when a string is a direct child of a host
node. In that case, we won't reconcile it as a child fiber. In
terms of fibers, it is terminal. However, the host config special
cases it.
It is kind of unfortunate that we have to special case this kind
of child in the HostConfig. It would be nice to unify this with
other types of child instances. Text nodes have some weird special
cases, but those special cases tend to *vary* by environment.
They're not the same special cases so not sure how valuable it is
to have a special protocol and special types for it.
We currently treat nested arrays as a unique namespace from top
level children. So that these won't share key namespaces. This
adds a new fiber type that will represent the position of a
fragment.
This is only used for nested arrays. Even if you return an array
from a composite component, we don't need this since they share
namespace with a single top level component.
This still doesn't implement the complete reconciliation
algorthim in child fiber. That's coming later.
Otherwise Flow finds obscure files in docs/vendor.
Seems like you'd only get them if you have a local checkout of docs Ruby dependencies.
This explains why Travis didn't fail.
* Initial pass at the easy case of updates (updates that start at the root).
* Don't expect an extra componentWillUnmount call
It was fixed in #6613.
* Remove duplicate expectations from the test
* Fix style issues
* Make naming consistent throughout the tests
* receiveComponent() does not accept safely argument
* Assert that lifecycle and refs fire for error message
* Add more tests for mounting
* Do not call componentWillMount twice on error boundary
* Document more of existing behavior in tests
* Do not call componentWillUnmount() when aborting mounting
Previously, we would call componentWillUnmount() safely on the tree whenever we abort mounting it. However this is likely risky because the tree was never mounted in the first place.
People shouldn't hold resources in componentWillMount() so it's safe to say that we can skip componentWillUnmount() if componentDidMount() was never called.
Here, we introduce a new flag. If we abort during mounting, we will not call componentWillUnmount(). However if we abort during an update, it is safe to call componentWillUnmount() because the previous tree has been mounted by now.
* Consistently display error messages in tests
* Add more logging to tests and remove redundant one
* Refactor tests
* Split complicated tests into smaller ones
* Assert clean unmounting
* Add assertions about update hooks
* Add more tests to document existing behavior and remove irrelevant details
* Verify we can recover from error state
* Fix lint
* Error in boundary’s componentWillMount should propagate up
This test is currently failing.
* Move calling componentWillMount() into mountComponent()
This removes the unnecessary non-recursive skipLifecycle check.
It fixes the previously failing test that verifies that if a boundary throws in its own componentWillMount(), the error will propagate.
* Remove extra whitespace