Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d9393ee
Add Fragment fiber type
sebmarkbage Sep 13, 2016
e8c1cb4
Add Text node types
sebmarkbage Sep 14, 2016
d7322d8
Silence Fiber warning when the feature flag is on
sebmarkbage Sep 13, 2016
e05ab67
Fix MultiChild tests so they work with Fiber
sebmarkbage Sep 13, 2016
8c7409b
Add comment about bug in yields
sebmarkbage Sep 15, 2016
4ed67f1
Enable text updates in ReactNoop
sebmarkbage Sep 19, 2016
dff0faf
Fiber child reconciliation
sebmarkbage Sep 20, 2016
51f2bf9
Add index field to each fiber
sebmarkbage Sep 21, 2016
bcbceae
Don't track side-effects unless needed
sebmarkbage Sep 21, 2016
c49a91c
Fast path for create child
sebmarkbage Oct 4, 2016
be0acf8
Deletion tracking
sebmarkbage Oct 4, 2016
76725fe
Tag the fiber with the kind of side-effect that was applied to it
sebmarkbage Oct 4, 2016
86e854e
Append deletions to the effect list
sebmarkbage Oct 4, 2016
eabed69
Move child updates to use the reconciled effects
sebmarkbage Oct 5, 2016
31ca1e7
Remove beginWork shortcut
sebmarkbage Oct 6, 2016
0262e70
Reset effect list when we recompute children
sebmarkbage Oct 7, 2016
f3d7116
Always override priority level when visiting children
sebmarkbage Oct 7, 2016
40989f8
Call componentWillUnmount during deletion phase
sebmarkbage Oct 7, 2016
8360088
Fire componentDidMount/componentDidUpdate life-cycles
sebmarkbage Oct 7, 2016
48cf81c
Resolve ref callbacks
sebmarkbage Oct 7, 2016
8721ec1
Invoke all null ref calls before any new ref calls
sebmarkbage Oct 7, 2016
d9efde7
Fix resuming bug
sebmarkbage Oct 10, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Resolve ref callbacks
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.
  • Loading branch information
sebmarkbage committed Oct 17, 2016
commit 48cf81c2f6f3f2d4a7f34175d8f54a9e0c348efc
5 changes: 5 additions & 0 deletions src/renderers/shared/fiber/ReactChildFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,13 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
if (current == null || current.type !== element.type) {
// Insert
const created = createFiberFromElement(element, priority);
created.ref = element.ref;
created.return = returnFiber;
return created;
} else {
// Move based on index
const existing = useFiber(current, priority);
existing.ref = element.ref;
existing.pendingProps = element.props;
existing.return = returnFiber;
return existing;
Expand Down Expand Up @@ -317,6 +319,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(newChild, priority);
created.ref = newChild.ref;
created.return = returnFiber;
return created;
}
Expand Down Expand Up @@ -643,6 +646,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
if (child.type === element.type) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, priority);
existing.ref = element.ref;
existing.pendingProps = element.props;
existing.return = returnFiber;
return existing;
Expand All @@ -657,6 +661,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
}

const created = createFiberFromElement(element, priority);
created.ref = element.ref;
created.return = returnFiber;
return created;
}
Expand Down
52 changes: 40 additions & 12 deletions src/renderers/shared/fiber/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,29 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
const insertBefore = config.insertBefore;
const removeChild = config.removeChild;

function detachRef(current : Fiber) {
const ref = current.ref;
if (ref) {
ref(null);
}
}

function attachRef(current : ?Fiber, finishedWork : Fiber, instance : any) {
const ref = finishedWork.ref;
if (current) {
const currentRef = current.ref;
if (currentRef && currentRef !== ref) {
// TODO: This needs to be done in a separate pass before any other refs
// gets resolved. Otherwise we might invoke them in the wrong order
// when the same ref switches between two components.
currentRef(null);
}
}
if (ref) {
ref(instance);
}
}

function getHostParent(fiber : Fiber) : ?I {
let parent = fiber.return;
while (parent) {
Expand Down Expand Up @@ -163,9 +186,6 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
// Recursively delete all host nodes from the parent.
// TODO: Error handling.
const parent = getHostParent(current);
if (!parent) {
return;
}
// We only have the top Fiber that was inserted but we need recurse down its
// children to find all the terminal nodes.
// TODO: Call componentWillUnmount on all classes as needed. Recurse down
Expand All @@ -177,7 +197,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
commitNestedUnmounts(node);
// After all the children have unmounted, it is now safe to remove the
// node from the tree.
removeChild(parent, node.stateNode);
if (parent) {
removeChild(parent, node.stateNode);
}
} else {
commitUnmount(node);
if (node.child) {
Expand All @@ -202,10 +224,16 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
function commitUnmount(current : Fiber) : void {
switch (current.tag) {
case ClassComponent: {
detachRef(current);
const instance = current.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
instance.componentWillUnmount();
}
return;
}
case HostComponent: {
detachRef(current);
return;
}
}
}
Expand Down Expand Up @@ -239,7 +267,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
finishedWork.callbackList = null;
callCallbacks(callbackList, instance);
}
// TODO: Fire update refs
attachRef(current, finishedWork, instance);
return;
}
case HostContainer: {
Expand All @@ -251,14 +279,14 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
return;
}
case HostComponent: {
if (finishedWork.stateNode == null || !current) {
throw new Error('This should only be done during updates.');
}
// Commit the work prepared earlier.
const newProps = finishedWork.memoizedProps;
const oldProps = current.memoizedProps;
const instance : I = finishedWork.stateNode;
commitUpdate(instance, oldProps, newProps);
if (instance != null && current) {
// Commit the work prepared earlier.
const newProps = finishedWork.memoizedProps;
const oldProps = current.memoizedProps;
commitUpdate(instance, oldProps, newProps);
}
attachRef(current, finishedWork, instance);
return;
}
case HostText: {
Expand Down
4 changes: 4 additions & 0 deletions src/renderers/shared/fiber/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
// TODO: This seems like unnecessary duplication.
workInProgress.stateNode = instance;
workInProgress.output = instance;
if (workInProgress.ref) {
// If there is a ref on a host node we need to schedule a callback
markUpdate(workInProgress);
}
}
workInProgress.memoizedProps = newProps;
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,72 @@ describe('ReactIncrementalSideEffects', () => {

});

it('invokes ref callbacks after insertion/update/unmount', () => {

var classInstance = null;

var ops = [];

class ClassComponent extends React.Component {
render() {
classInstance = this;
return <span />;
}
}

function FunctionalComponent(props) {
return <span />;
}

function Foo(props) {
return (
props.show ?
<div>
<ClassComponent ref={n => ops.push(n)} />
<FunctionalComponent ref={n => ops.push(n)} />
<div ref={n => ops.push(n)} />
</div> :
null
);
}

ReactNoop.render(<Foo show={true} />);
ReactNoop.flush();
expect(ops).toEqual([
classInstance,
// no call for functional components
div(),
]);

ops = [];

// Refs that switch function instances get reinvoked
ReactNoop.render(<Foo show={true} />);
ReactNoop.flush();
expect(ops).toEqual([
// TODO: All detach should happen first. Currently they're interleaved.
// detach
null,
// reattach
classInstance,
// detach
null,
// reattach
div(),
]);

ops = [];

ReactNoop.render(<Foo show={false} />);
ReactNoop.flush();
expect(ops).toEqual([
// unmount
null,
null,
]);

});

// TODO: Test that mounts, updates, refs, unmounts and deletions happen in the
// expected way for aborted and resumed render life-cycles.

Expand Down