Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions src/renderers/dom/fiber/ReactDOMFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
101 changes: 101 additions & 0 deletions src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
}
});
12 changes: 12 additions & 0 deletions src/renderers/noop/ReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions src/renderers/shared/fiber/ReactFiberClassComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 ?
Expand Down
28 changes: 21 additions & 7 deletions src/renderers/shared/fiber/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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: {
Expand Down
21 changes: 17 additions & 4 deletions src/renderers/shared/fiber/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ if (__DEV__) {
var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
}

var { findCurrentHostFiber } = require('ReactFiberTreeReflection');

type Deadline = {
timeRemaining : () => number
};
Expand Down Expand Up @@ -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> = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TI ~= TextInstance?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea

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);

Expand Down Expand Up @@ -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) {
Expand All @@ -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;
},

};

};
8 changes: 8 additions & 0 deletions src/renderers/shared/fiber/ReactFiberScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to be the same comment as above? How is setting the effectTag to Update equal to clearing it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearing the "placement" part of it.

// any life-cycles like componentDidMount gets called.
effectfulFiber.effectTag = Update;
break;
}
case Update: {
Expand Down Expand Up @@ -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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment doesn't apply to the line below anymore. It used to refer to effectTag assignment which has been moved.

effectfulFiber = next;
}

Expand Down
Loading