mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge pull request #8083 from sebmarkbage/fiberfinddomnode
[Fiber] Implement findDOMNode and isMounted
This commit is contained in:
@@ -146,6 +146,18 @@ var ReactDOM = {
|
||||
}
|
||||
},
|
||||
|
||||
findDOMNode(componentOrElement : Element | ?ReactComponent<any, any, any>) : null | Element | Text {
|
||||
if (componentOrElement == null) {
|
||||
return null;
|
||||
}
|
||||
// Unsound duck typing.
|
||||
const component = (componentOrElement : any);
|
||||
if (component.nodeType === 1) {
|
||||
return component;
|
||||
}
|
||||
return DOMRenderer.findHostInstance(component);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = ReactDOM;
|
||||
|
||||
@@ -64,5 +64,106 @@ describe('ReactDOMFiber', () => {
|
||||
|
||||
expect(container.textContent).toEqual('10');
|
||||
});
|
||||
|
||||
it('finds the DOM Text node of a string child', () => {
|
||||
class Text extends React.Component {
|
||||
render() {
|
||||
return this.props.value;
|
||||
}
|
||||
}
|
||||
|
||||
let instance = null;
|
||||
ReactDOM.render(
|
||||
<Text value="foo" ref={ref => instance = ref} />,
|
||||
container
|
||||
);
|
||||
|
||||
const textNode = ReactDOM.findDOMNode(instance);
|
||||
expect(textNode).toBe(container.firstChild);
|
||||
expect(textNode.nodeType).toBe(3);
|
||||
expect(textNode.nodeValue).toBe('foo');
|
||||
});
|
||||
|
||||
it('finds the first child when a component returns a fragment', () => {
|
||||
class Fragment extends React.Component {
|
||||
render() {
|
||||
return [
|
||||
<div />,
|
||||
<span />,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
let instance = null;
|
||||
ReactDOM.render(
|
||||
<Fragment ref={ref => instance = ref} />,
|
||||
container
|
||||
);
|
||||
|
||||
expect(container.childNodes.length).toBe(2);
|
||||
|
||||
const firstNode = ReactDOM.findDOMNode(instance);
|
||||
expect(firstNode).toBe(container.firstChild);
|
||||
expect(firstNode.tagName).toBe('DIV');
|
||||
});
|
||||
|
||||
it('finds the first child even when fragment is nested', () => {
|
||||
class Wrapper extends React.Component {
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
class Fragment extends React.Component {
|
||||
render() {
|
||||
return [
|
||||
<Wrapper><div /></Wrapper>,
|
||||
<span />,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
let instance = null;
|
||||
ReactDOM.render(
|
||||
<Fragment ref={ref => instance = ref} />,
|
||||
container
|
||||
);
|
||||
|
||||
expect(container.childNodes.length).toBe(2);
|
||||
|
||||
const firstNode = ReactDOM.findDOMNode(instance);
|
||||
expect(firstNode).toBe(container.firstChild);
|
||||
expect(firstNode.tagName).toBe('DIV');
|
||||
});
|
||||
|
||||
it('finds the first child even when first child renders null', () => {
|
||||
class NullComponent extends React.Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class Fragment extends React.Component {
|
||||
render() {
|
||||
return [
|
||||
<NullComponent />,
|
||||
<div />,
|
||||
<span />,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
let instance = null;
|
||||
ReactDOM.render(
|
||||
<Fragment ref={ref => instance = ref} />,
|
||||
container
|
||||
);
|
||||
|
||||
expect(container.childNodes.length).toBe(2);
|
||||
|
||||
const firstNode = ReactDOM.findDOMNode(instance);
|
||||
expect(firstNode).toBe(container.firstChild);
|
||||
expect(firstNode.tagName).toBe('DIV');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -154,6 +154,18 @@ var ReactNoop = {
|
||||
}
|
||||
},
|
||||
|
||||
findInstance(componentOrElement : Element | ?ReactComponent<any, any, any>) : null | Instance | TextInstance {
|
||||
if (componentOrElement == null) {
|
||||
return null;
|
||||
}
|
||||
// Unsound duck typing.
|
||||
const component = (componentOrElement : any);
|
||||
if (component.tag === TERMINAL_TAG || component.tag === TEXT_TAG) {
|
||||
return component;
|
||||
}
|
||||
return NoopRenderer.findHostInstance(component);
|
||||
},
|
||||
|
||||
flushAnimationPri() {
|
||||
var cb = scheduledAnimationCallback;
|
||||
if (cb === null) {
|
||||
|
||||
@@ -23,6 +23,7 @@ var {
|
||||
addCallbackToQueue,
|
||||
mergeUpdateQueue,
|
||||
} = require('ReactFiberUpdateQueue');
|
||||
var { isMounted } = require('ReactFiberTreeReflection');
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
|
||||
module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : PriorityLevel) => void) {
|
||||
@@ -39,6 +40,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : Priori
|
||||
|
||||
// Class component state updater
|
||||
const updater = {
|
||||
isMounted,
|
||||
enqueueSetState(instance, partialState) {
|
||||
const fiber = ReactInstanceMap.get(instance);
|
||||
const updateQueue = fiber.updateQueue ?
|
||||
|
||||
@@ -182,15 +182,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
}
|
||||
}
|
||||
|
||||
function commitDeletion(current : Fiber) : void {
|
||||
// Recursively delete all host nodes from the parent.
|
||||
// TODO: Error handling.
|
||||
const parent = getHostParent(current);
|
||||
function unmountHostComponents(parent, current) {
|
||||
// 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
|
||||
// removed HostComponents but don't call removeChild on already removed
|
||||
// children.
|
||||
let node : Fiber = current;
|
||||
while (true) {
|
||||
if (node.tag === HostComponent || node.tag === HostText) {
|
||||
@@ -221,6 +215,26 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
}
|
||||
}
|
||||
|
||||
function commitDeletion(current : Fiber) : void {
|
||||
// Recursively delete all host nodes from the parent.
|
||||
// TODO: Error handling.
|
||||
const parent = getHostParent(current);
|
||||
|
||||
unmountHostComponents(parent, current);
|
||||
|
||||
// Cut off the return pointers to disconnect it from the tree. Ideally, we
|
||||
// should clear the child pointer of the parent alternate to let this
|
||||
// get GC:ed but we don't know which for sure which parent is the current
|
||||
// one so we'll settle for GC:ing the subtree of this child. This child
|
||||
// itself will be GC:ed when the parent updates the next time.
|
||||
current.return = null;
|
||||
current.child = null;
|
||||
if (current.alternate) {
|
||||
current.alternate.child = null;
|
||||
current.alternate.return = null;
|
||||
}
|
||||
}
|
||||
|
||||
function commitUnmount(current : Fiber) : void {
|
||||
switch (current.tag) {
|
||||
case ClassComponent: {
|
||||
|
||||
@@ -24,6 +24,8 @@ if (__DEV__) {
|
||||
var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
|
||||
}
|
||||
|
||||
var { findCurrentHostFiber } = require('ReactFiberTreeReflection');
|
||||
|
||||
type Deadline = {
|
||||
timeRemaining : () => number
|
||||
};
|
||||
@@ -58,17 +60,20 @@ export type HostConfig<T, P, I, TI, C> = {
|
||||
|
||||
type OpaqueNode = Fiber;
|
||||
|
||||
export type Reconciler<C, I> = {
|
||||
export type Reconciler<C, I, TI> = {
|
||||
mountContainer(element : ReactElement<any>, containerInfo : C) : OpaqueNode,
|
||||
updateContainer(element : ReactElement<any>, container : OpaqueNode) : void,
|
||||
unmountContainer(container : OpaqueNode) : void,
|
||||
performWithPriority(priorityLevel : PriorityLevel, fn : Function) : void,
|
||||
|
||||
// Used to extract the return value from the initial render. Legacy API.
|
||||
getPublicRootInstance(container : OpaqueNode) : (ReactComponent<any, any, any> | I | null),
|
||||
getPublicRootInstance(container : OpaqueNode) : (ReactComponent<any, any, any> | TI | I | null),
|
||||
|
||||
// Use for findDOMNode/findHostNode. Legacy API.
|
||||
findHostInstance(component : ReactComponent<any, any, any>) : I | TI | null,
|
||||
};
|
||||
|
||||
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) : Reconciler<C, I> {
|
||||
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) : Reconciler<C, I, TI> {
|
||||
|
||||
var { scheduleWork, performWithPriority } = ReactFiberScheduler(config);
|
||||
|
||||
@@ -122,7 +127,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
|
||||
|
||||
performWithPriority,
|
||||
|
||||
getPublicRootInstance(container : OpaqueNode) : (ReactComponent<any, any, any> | I | null) {
|
||||
getPublicRootInstance(container : OpaqueNode) : (ReactComponent<any, any, any> | I | TI | null) {
|
||||
const root : FiberRoot = (container.stateNode : any);
|
||||
const containerFiber = root.current;
|
||||
if (!containerFiber.child) {
|
||||
@@ -131,6 +136,14 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
|
||||
return containerFiber.child.stateNode;
|
||||
},
|
||||
|
||||
findHostInstance(component : ReactComponent<any, any, any>) : I | TI | null {
|
||||
const fiber = findCurrentHostFiber(component);
|
||||
if (!fiber) {
|
||||
return null;
|
||||
}
|
||||
return fiber.stateNode;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@@ -119,12 +119,18 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
switch (effectfulFiber.effectTag) {
|
||||
case Placement: {
|
||||
commitInsertion(effectfulFiber);
|
||||
// Clear the effect tag so that we know that this is inserted, before
|
||||
// any life-cycles like componentDidMount gets called.
|
||||
effectfulFiber.effectTag = NoWork;
|
||||
break;
|
||||
}
|
||||
case PlacementAndUpdate: {
|
||||
commitInsertion(effectfulFiber);
|
||||
const current = effectfulFiber.alternate;
|
||||
commitWork(current, effectfulFiber);
|
||||
// Clear the effect tag so that we know that this is inserted, before
|
||||
// any life-cycles like componentDidMount gets called.
|
||||
effectfulFiber.effectTag = Update;
|
||||
break;
|
||||
}
|
||||
case Update: {
|
||||
@@ -156,6 +162,8 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
// and lastEffect since they're on every node, not just the effectful
|
||||
// ones. So we have to clean everything as we reuse nodes anyway.
|
||||
effectfulFiber.nextEffect = null;
|
||||
// Ensure that we reset the effectTag here so that we can rely on effect
|
||||
// tags to reason about the current life-cycle.
|
||||
effectfulFiber = next;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactFiberTreeReflection
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
|
||||
var ReactInstanceMap = require('ReactInstanceMap');
|
||||
|
||||
var {
|
||||
HostContainer,
|
||||
HostComponent,
|
||||
HostText,
|
||||
} = require('ReactTypeOfWork');
|
||||
|
||||
var {
|
||||
NoEffect,
|
||||
Placement,
|
||||
} = require('ReactTypeOfSideEffect');
|
||||
|
||||
function isFiberMounted(fiber : Fiber) : boolean {
|
||||
let node = fiber;
|
||||
if (!fiber.alternate) {
|
||||
// If there is no alternate, this might be a new tree that isn't inserted
|
||||
// yet. If it is, then it will have a pending insertion effect on it.
|
||||
if ((node.effectTag & Placement) !== NoEffect) {
|
||||
return false;
|
||||
}
|
||||
while (node.return) {
|
||||
node = node.return;
|
||||
if ((node.effectTag & Placement) !== NoEffect) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while (node.return) {
|
||||
node = node.return;
|
||||
}
|
||||
}
|
||||
if (node.tag === HostContainer) {
|
||||
// TODO: Check if this was a nested HostContainer when used with
|
||||
// renderContainerIntoSubtree.
|
||||
return true;
|
||||
}
|
||||
// If we didn't hit the root, that means that we're in an disconnected tree.
|
||||
return false;
|
||||
}
|
||||
|
||||
exports.isMounted = function(component : ReactComponent<any, any, any>) : boolean {
|
||||
var fiber : ?Fiber = ReactInstanceMap.get(component);
|
||||
if (!fiber) {
|
||||
return false;
|
||||
}
|
||||
return isFiberMounted(fiber);
|
||||
};
|
||||
|
||||
exports.findCurrentHostFiber = function(component : ReactComponent<any, any, any>) : Fiber | null {
|
||||
let parent = ReactInstanceMap.get(component);
|
||||
if (!parent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isFiberMounted(parent)) {
|
||||
// First check if this node itself is mounted.
|
||||
return null;
|
||||
}
|
||||
|
||||
let didTryOtherTree = false;
|
||||
|
||||
// Next we'll drill down this component to find the first HostComponent/Text.
|
||||
let node : Fiber = parent;
|
||||
while (true) {
|
||||
if ((node.effectTag & Placement) !== NoEffect || !node.return) {
|
||||
// If any node along the way was deleted, or is an insertion, that means
|
||||
// that we're actually in a work in progress to update this component with
|
||||
// a different component. We need to look in the "current" fiber instead.
|
||||
if (!parent.alternate) {
|
||||
return null;
|
||||
}
|
||||
if (didTryOtherTree) {
|
||||
// Safety, to avoid an infinite loop if something goes wrong.
|
||||
throw new Error('This should never hit this infinite loop.');
|
||||
}
|
||||
didTryOtherTree = true;
|
||||
node = parent = parent.alternate;
|
||||
continue;
|
||||
}
|
||||
if (node.tag === HostComponent || node.tag === HostText) {
|
||||
return node;
|
||||
} else if (node.child) {
|
||||
// TODO: Coroutines need to visit the stateNode.
|
||||
node = node.child;
|
||||
continue;
|
||||
}
|
||||
if (node === parent) {
|
||||
return null;
|
||||
}
|
||||
while (!node.sibling) {
|
||||
if (!node.return || node.return === parent) {
|
||||
return null;
|
||||
}
|
||||
node = node.return;
|
||||
}
|
||||
node = node.sibling;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React;
|
||||
var ReactNoop;
|
||||
|
||||
describe('ReactIncrementalReflection', () => {
|
||||
beforeEach(() => {
|
||||
React = require('React');
|
||||
ReactNoop = require('ReactNoop');
|
||||
});
|
||||
|
||||
it('handles isMounted even when the initial render is deferred', () => {
|
||||
|
||||
let ops = [];
|
||||
|
||||
const instances = [];
|
||||
|
||||
const Component = React.createClass({
|
||||
componentWillMount() {
|
||||
instances.push(this);
|
||||
ops.push('componentWillMount', this.isMounted());
|
||||
},
|
||||
componentDidMount() {
|
||||
ops.push('componentDidMount', this.isMounted());
|
||||
},
|
||||
render() {
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
|
||||
function Foo() {
|
||||
return <Component />;
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
|
||||
// Render part way through but don't yet commit the updates.
|
||||
ReactNoop.flushDeferredPri(20);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'componentWillMount', false,
|
||||
]);
|
||||
|
||||
expect(instances[0].isMounted()).toBe(false);
|
||||
|
||||
ops = [];
|
||||
|
||||
// Render the rest and commit the updates.
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'componentDidMount', true,
|
||||
]);
|
||||
|
||||
expect(instances[0].isMounted()).toBe(true);
|
||||
|
||||
});
|
||||
|
||||
it('handles isMounted when an unmount is deferred', () => {
|
||||
|
||||
let ops = [];
|
||||
|
||||
const instances = [];
|
||||
|
||||
const Component = React.createClass({
|
||||
componentWillMount() {
|
||||
instances.push(this);
|
||||
},
|
||||
componentWillUnmount() {
|
||||
ops.push('componentWillUnmount', this.isMounted());
|
||||
},
|
||||
render() {
|
||||
ops.push('Component');
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
|
||||
function Other() {
|
||||
ops.push('Other');
|
||||
return <span />;
|
||||
}
|
||||
|
||||
function Foo(props) {
|
||||
return props.mount ? <Component /> : <Other />;
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo mount={true} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual(['Component']);
|
||||
ops = [];
|
||||
|
||||
expect(instances[0].isMounted()).toBe(true);
|
||||
|
||||
ReactNoop.render(<Foo mount={false} />);
|
||||
// Render part way through but don't yet commit the updates so it is not
|
||||
// fully unmounted yet.
|
||||
ReactNoop.flushDeferredPri(20);
|
||||
|
||||
expect(ops).toEqual(['Other']);
|
||||
ops = [];
|
||||
|
||||
expect(instances[0].isMounted()).toBe(true);
|
||||
|
||||
// Finish flushing the unmount.
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'componentWillUnmount', true,
|
||||
]);
|
||||
|
||||
expect(instances[0].isMounted()).toBe(false);
|
||||
|
||||
});
|
||||
|
||||
it('finds no node before insertion and correct node before deletion', () => {
|
||||
|
||||
let ops = [];
|
||||
|
||||
let classInstance = null;
|
||||
|
||||
class Component extends React.Component {
|
||||
componentWillMount() {
|
||||
classInstance = this;
|
||||
ops.push('componentWillMount', ReactNoop.findInstance(this));
|
||||
}
|
||||
componentDidMount() {
|
||||
ops.push('componentDidMount', ReactNoop.findInstance(this));
|
||||
}
|
||||
componentWillUpdate() {
|
||||
ops.push('componentWillUpdate', ReactNoop.findInstance(this));
|
||||
}
|
||||
componentDidUpdate() {
|
||||
ops.push('componentDidUpdate', ReactNoop.findInstance(this));
|
||||
}
|
||||
render() {
|
||||
ops.push('render');
|
||||
return this.props.step < 2 ?
|
||||
<span ref={ref => this.span = ref} /> :
|
||||
this.props.step === 2 ?
|
||||
<div ref={ref => this.div = ref} /> :
|
||||
null;
|
||||
}
|
||||
}
|
||||
|
||||
function Sibling() {
|
||||
// Sibling is used to assert that we've rendered past the first component.
|
||||
ops.push('render sibling');
|
||||
return <span />;
|
||||
}
|
||||
|
||||
function Foo(props) {
|
||||
return [
|
||||
<Component step={props.step} />,
|
||||
<Sibling />,
|
||||
];
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo step={0} />);
|
||||
// Flush past Component but don't complete rendering everything yet.
|
||||
ReactNoop.flushDeferredPri(30);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'componentWillMount', null,
|
||||
'render',
|
||||
'render sibling',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
expect(classInstance).toBeDefined();
|
||||
// The instance has been complete but is still not committed so it should
|
||||
// not find any host nodes in it.
|
||||
expect(ReactNoop.findInstance(classInstance)).toBe(null);
|
||||
|
||||
ReactNoop.flush();
|
||||
|
||||
const hostSpan = classInstance.span;
|
||||
expect(hostSpan).toBeDefined();
|
||||
|
||||
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'componentDidMount', hostSpan,
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
// Flush next step which will cause an update but not yet render a new host
|
||||
// node.
|
||||
ReactNoop.render(<Foo step={1} />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'componentWillUpdate', hostSpan,
|
||||
'render',
|
||||
'render sibling',
|
||||
'componentDidUpdate', hostSpan,
|
||||
]);
|
||||
|
||||
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
|
||||
|
||||
ops = [];
|
||||
|
||||
// The next step will render a new host node but won't get committed yet.
|
||||
// We expect this to mutate the original Fiber.
|
||||
ReactNoop.render(<Foo step={2} />);
|
||||
ReactNoop.flushDeferredPri(30);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'componentWillUpdate', hostSpan,
|
||||
'render',
|
||||
'render sibling',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
// This should still be the host span.
|
||||
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
|
||||
|
||||
// When we finally flush the tree it will get committed.
|
||||
ReactNoop.flush();
|
||||
|
||||
const hostDiv = classInstance.div;
|
||||
|
||||
expect(hostDiv).toBeDefined();
|
||||
expect(hostSpan).not.toBe(hostDiv);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'componentDidUpdate', hostDiv,
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
// We should now find the new host node.
|
||||
expect(ReactNoop.findInstance(classInstance)).toBe(hostDiv);
|
||||
|
||||
// Finally we will render to null but not yet commit it.
|
||||
ReactNoop.render(<Foo step={3} />);
|
||||
ReactNoop.flushDeferredPri(25);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'componentWillUpdate', hostDiv,
|
||||
'render',
|
||||
'render sibling',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
// This should still be the host div since the deletion is not committed.
|
||||
expect(ReactNoop.findInstance(classInstance)).toBe(hostDiv);
|
||||
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'componentDidUpdate', null,
|
||||
]);
|
||||
|
||||
// This should still be the host div since the deletion is not committed.
|
||||
expect(ReactNoop.findInstance(classInstance)).toBe(null);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user