Skip to content

Commit 7bcad0a

Browse files
authored
Merge pull request #8083 from sebmarkbage/fiberfinddomnode
[Fiber] Implement findDOMNode and isMounted
2 parents 51e1937 + 8e2d7f8 commit 7bcad0a

9 files changed

+564
-11
lines changed

src/renderers/dom/fiber/ReactDOMFiber.js

+12
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,18 @@ var ReactDOM = {
146146
}
147147
},
148148

149+
findDOMNode(componentOrElement : Element | ?ReactComponent<any, any, any>) : null | Element | Text {
150+
if (componentOrElement == null) {
151+
return null;
152+
}
153+
// Unsound duck typing.
154+
const component = (componentOrElement : any);
155+
if (component.nodeType === 1) {
156+
return component;
157+
}
158+
return DOMRenderer.findHostInstance(component);
159+
},
160+
149161
};
150162

151163
module.exports = ReactDOM;

src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js

+101
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,106 @@ describe('ReactDOMFiber', () => {
6464

6565
expect(container.textContent).toEqual('10');
6666
});
67+
68+
it('finds the DOM Text node of a string child', () => {
69+
class Text extends React.Component {
70+
render() {
71+
return this.props.value;
72+
}
73+
}
74+
75+
let instance = null;
76+
ReactDOM.render(
77+
<Text value="foo" ref={ref => instance = ref} />,
78+
container
79+
);
80+
81+
const textNode = ReactDOM.findDOMNode(instance);
82+
expect(textNode).toBe(container.firstChild);
83+
expect(textNode.nodeType).toBe(3);
84+
expect(textNode.nodeValue).toBe('foo');
85+
});
86+
87+
it('finds the first child when a component returns a fragment', () => {
88+
class Fragment extends React.Component {
89+
render() {
90+
return [
91+
<div />,
92+
<span />,
93+
];
94+
}
95+
}
96+
97+
let instance = null;
98+
ReactDOM.render(
99+
<Fragment ref={ref => instance = ref} />,
100+
container
101+
);
102+
103+
expect(container.childNodes.length).toBe(2);
104+
105+
const firstNode = ReactDOM.findDOMNode(instance);
106+
expect(firstNode).toBe(container.firstChild);
107+
expect(firstNode.tagName).toBe('DIV');
108+
});
109+
110+
it('finds the first child even when fragment is nested', () => {
111+
class Wrapper extends React.Component {
112+
render() {
113+
return this.props.children;
114+
}
115+
}
116+
117+
class Fragment extends React.Component {
118+
render() {
119+
return [
120+
<Wrapper><div /></Wrapper>,
121+
<span />,
122+
];
123+
}
124+
}
125+
126+
let instance = null;
127+
ReactDOM.render(
128+
<Fragment ref={ref => instance = ref} />,
129+
container
130+
);
131+
132+
expect(container.childNodes.length).toBe(2);
133+
134+
const firstNode = ReactDOM.findDOMNode(instance);
135+
expect(firstNode).toBe(container.firstChild);
136+
expect(firstNode.tagName).toBe('DIV');
137+
});
138+
139+
it('finds the first child even when first child renders null', () => {
140+
class NullComponent extends React.Component {
141+
render() {
142+
return null;
143+
}
144+
}
145+
146+
class Fragment extends React.Component {
147+
render() {
148+
return [
149+
<NullComponent />,
150+
<div />,
151+
<span />,
152+
];
153+
}
154+
}
155+
156+
let instance = null;
157+
ReactDOM.render(
158+
<Fragment ref={ref => instance = ref} />,
159+
container
160+
);
161+
162+
expect(container.childNodes.length).toBe(2);
163+
164+
const firstNode = ReactDOM.findDOMNode(instance);
165+
expect(firstNode).toBe(container.firstChild);
166+
expect(firstNode.tagName).toBe('DIV');
167+
});
67168
}
68169
});

src/renderers/noop/ReactNoop.js

+12
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,18 @@ var ReactNoop = {
154154
}
155155
},
156156

157+
findInstance(componentOrElement : Element | ?ReactComponent<any, any, any>) : null | Instance | TextInstance {
158+
if (componentOrElement == null) {
159+
return null;
160+
}
161+
// Unsound duck typing.
162+
const component = (componentOrElement : any);
163+
if (component.tag === TERMINAL_TAG || component.tag === TEXT_TAG) {
164+
return component;
165+
}
166+
return NoopRenderer.findHostInstance(component);
167+
},
168+
157169
flushAnimationPri() {
158170
var cb = scheduledAnimationCallback;
159171
if (cb === null) {

src/renderers/shared/fiber/ReactFiberClassComponent.js

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ var {
2323
addCallbackToQueue,
2424
mergeUpdateQueue,
2525
} = require('ReactFiberUpdateQueue');
26+
var { isMounted } = require('ReactFiberTreeReflection');
2627
var ReactInstanceMap = require('ReactInstanceMap');
2728

2829
module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : PriorityLevel) => void) {
@@ -39,6 +40,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber, priorityLevel : Priori
3940

4041
// Class component state updater
4142
const updater = {
43+
isMounted,
4244
enqueueSetState(instance, partialState) {
4345
const fiber = ReactInstanceMap.get(instance);
4446
const updateQueue = fiber.updateQueue ?

src/renderers/shared/fiber/ReactFiberCommitWork.js

+21-7
Original file line numberDiff line numberDiff line change
@@ -182,15 +182,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
182182
}
183183
}
184184

185-
function commitDeletion(current : Fiber) : void {
186-
// Recursively delete all host nodes from the parent.
187-
// TODO: Error handling.
188-
const parent = getHostParent(current);
185+
function unmountHostComponents(parent, current) {
189186
// We only have the top Fiber that was inserted but we need recurse down its
190187
// children to find all the terminal nodes.
191-
// TODO: Call componentWillUnmount on all classes as needed. Recurse down
192-
// removed HostComponents but don't call removeChild on already removed
193-
// children.
194188
let node : Fiber = current;
195189
while (true) {
196190
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>) {
221215
}
222216
}
223217

218+
function commitDeletion(current : Fiber) : void {
219+
// Recursively delete all host nodes from the parent.
220+
// TODO: Error handling.
221+
const parent = getHostParent(current);
222+
223+
unmountHostComponents(parent, current);
224+
225+
// Cut off the return pointers to disconnect it from the tree. Ideally, we
226+
// should clear the child pointer of the parent alternate to let this
227+
// get GC:ed but we don't know which for sure which parent is the current
228+
// one so we'll settle for GC:ing the subtree of this child. This child
229+
// itself will be GC:ed when the parent updates the next time.
230+
current.return = null;
231+
current.child = null;
232+
if (current.alternate) {
233+
current.alternate.child = null;
234+
current.alternate.return = null;
235+
}
236+
}
237+
224238
function commitUnmount(current : Fiber) : void {
225239
switch (current.tag) {
226240
case ClassComponent: {

src/renderers/shared/fiber/ReactFiberReconciler.js

+17-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ if (__DEV__) {
2424
var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
2525
}
2626

27+
var { findCurrentHostFiber } = require('ReactFiberTreeReflection');
28+
2729
type Deadline = {
2830
timeRemaining : () => number
2931
};
@@ -58,17 +60,20 @@ export type HostConfig<T, P, I, TI, C> = {
5860

5961
type OpaqueNode = Fiber;
6062

61-
export type Reconciler<C, I> = {
63+
export type Reconciler<C, I, TI> = {
6264
mountContainer(element : ReactElement<any>, containerInfo : C) : OpaqueNode,
6365
updateContainer(element : ReactElement<any>, container : OpaqueNode) : void,
6466
unmountContainer(container : OpaqueNode) : void,
6567
performWithPriority(priorityLevel : PriorityLevel, fn : Function) : void,
6668

6769
// Used to extract the return value from the initial render. Legacy API.
68-
getPublicRootInstance(container : OpaqueNode) : (ReactComponent<any, any, any> | I | null),
70+
getPublicRootInstance(container : OpaqueNode) : (ReactComponent<any, any, any> | TI | I | null),
71+
72+
// Use for findDOMNode/findHostNode. Legacy API.
73+
findHostInstance(component : ReactComponent<any, any, any>) : I | TI | null,
6974
};
7075

71-
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) : Reconciler<C, I> {
76+
module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) : Reconciler<C, I, TI> {
7277

7378
var { scheduleWork, performWithPriority } = ReactFiberScheduler(config);
7479

@@ -122,7 +127,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
122127

123128
performWithPriority,
124129

125-
getPublicRootInstance(container : OpaqueNode) : (ReactComponent<any, any, any> | I | null) {
130+
getPublicRootInstance(container : OpaqueNode) : (ReactComponent<any, any, any> | I | TI | null) {
126131
const root : FiberRoot = (container.stateNode : any);
127132
const containerFiber = root.current;
128133
if (!containerFiber.child) {
@@ -131,6 +136,14 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
131136
return containerFiber.child.stateNode;
132137
},
133138

139+
findHostInstance(component : ReactComponent<any, any, any>) : I | TI | null {
140+
const fiber = findCurrentHostFiber(component);
141+
if (!fiber) {
142+
return null;
143+
}
144+
return fiber.stateNode;
145+
},
146+
134147
};
135148

136149
};

src/renderers/shared/fiber/ReactFiberScheduler.js

+8
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,18 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
119119
switch (effectfulFiber.effectTag) {
120120
case Placement: {
121121
commitInsertion(effectfulFiber);
122+
// Clear the effect tag so that we know that this is inserted, before
123+
// any life-cycles like componentDidMount gets called.
124+
effectfulFiber.effectTag = NoWork;
122125
break;
123126
}
124127
case PlacementAndUpdate: {
125128
commitInsertion(effectfulFiber);
126129
const current = effectfulFiber.alternate;
127130
commitWork(current, effectfulFiber);
131+
// Clear the effect tag so that we know that this is inserted, before
132+
// any life-cycles like componentDidMount gets called.
133+
effectfulFiber.effectTag = Update;
128134
break;
129135
}
130136
case Update: {
@@ -156,6 +162,8 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
156162
// and lastEffect since they're on every node, not just the effectful
157163
// ones. So we have to clean everything as we reuse nodes anyway.
158164
effectfulFiber.nextEffect = null;
165+
// Ensure that we reset the effectTag here so that we can rely on effect
166+
// tags to reason about the current life-cycle.
159167
effectfulFiber = next;
160168
}
161169

0 commit comments

Comments
 (0)