Add functions to find the instance given a node
Ben Alpert committed
Oct 15, 2015 at 21:29 UTC
6d20556c78df6904254b8438bb588f7722ea602f
3 files changed
+119
-16
src/renderers/dom/client/ReactDOMComponentTree.js
+62
@@ -19,6 +19,9 @@ var invariant = require('invariant');
19
var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
20
var Flags = ReactDOMComponentFlags;
21
22
+var internalInstanceKey =
23
+ '__reactInternalInstance$' + Math.random().toString(36).slice(2);
24
+
25
/**
26
* Drill down (through composites and empty components) until we get a native or
27
* native text component.
@@ -41,6 +44,15 @@ function getRenderedNativeOrTextFromComponent(component) {
44
function precacheNode(inst, node) {
45
var nativeInst = getRenderedNativeOrTextFromComponent(inst);
46
nativeInst._nativeNode = node;
47
+ node[internalInstanceKey] = nativeInst;
48
+}
49
+
50
+function uncacheNode(inst) {
51
+ var node = inst._nativeNode;
52
+ if (node) {
53
+ delete node[internalInstanceKey];
54
+ inst._nativeNode = null;
55
+ }
56
}
57
58
/**
@@ -84,6 +96,53 @@ function precacheChildNodes(inst, node) {
96
inst._flags |= Flags.hasCachedChildNodes;
97
}
98
99
+/**
100
+ * Given a DOM node, return the closest ReactDOMComponent or
101
+ * ReactDOMTextComponent instance ancestor.
102
+ */
103
+function getClosestInstanceFromNode(node) {
104
+ if (node[internalInstanceKey]) {
105
+ return node[internalInstanceKey];
106
+ }
107
+
108
+ // Walk up the tree until we find an ancestor whose instance we have cached.
109
+ var parents = [];
110
+ while (!node[internalInstanceKey]) {
111
+ parents.push(node);
112
+ if (node.parentNode) {
113
+ node = node.parentNode;
114
+ } else {
115
+ // Top of the tree. This node must not be part of a React tree (or is
116
+ // unmounted, potentially).
117
+ return null;
118
+ }
119
+ }
120
+
121
+ var closest;
122
+ var inst;
123
+ for (; node && (inst = node[internalInstanceKey]); node = parents.pop()) {
124
+ closest = inst;
125
+ if (parents.length) {
126
+ precacheChildNodes(inst, node);
127
+ }
128
+ }
129
+
130
+ return closest;
131
+}
132
+
133
+/**
134
+ * Given a DOM node, return the ReactDOMComponent or ReactDOMTextComponent
135
+ * instance, or null if the node was not rendered by this React.
136
+ */
137
+function getInstanceFromNode(node) {
138
+ var inst = getClosestInstanceFromNode(node);
139
+ if (inst != null && inst._nativeNode === node) {
140
+ return inst;
141
+ } else {
142
+ return null;
143
+ }
144
+}
145
+
146
/**
147
* Given a ReactDOMComponent or ReactDOMTextComponent, return the corresponding
148
* DOM node.
@@ -114,9 +173,12 @@ function getNodeFromInstance(inst) {
173
}
174
175
var ReactDOMComponentTree = {
176
+ getClosestInstanceFromNode: getClosestInstanceFromNode,
177
+ getInstanceFromNode: getInstanceFromNode,
178
getNodeFromInstance: getNodeFromInstance,
179
precacheChildNodes: precacheChildNodes,
180
precacheNode: precacheNode,
181
+ uncacheNode: uncacheNode,
182
};
183
184
module.exports = ReactDOMComponentTree;
src/renderers/dom/client/__tests__/ReactDOMComponentTree-test.js
+47
@@ -14,6 +14,7 @@
14
describe('ReactDOMComponentTree', function() {
15
var React;
16
var ReactDOM;
17
+ var ReactDOMComponentTree;
18
var ReactDOMServer;
19
20
function renderMarkupIntoDocument(elt) {
@@ -26,6 +27,7 @@ describe('ReactDOMComponentTree', function() {
27
beforeEach(function() {
28
React = require('React');
29
ReactDOM = require('ReactDOM');
30
+ ReactDOMComponentTree = require('ReactDOMComponentTree');
31
ReactDOMServer = require('ReactDOMServer');
32
});
33
@@ -59,4 +61,49 @@ describe('ReactDOMComponentTree', function() {
61
expect(renderAndGetRef('input')).toBe('INPUT');
62
});
63
64
+ it('finds instances for nodes', function() {
65
+ var Component = React.createClass({
66
+ render: function() {
67
+ return (
68
+ <div>
69
+ <h1>hello</h1>
70
+ <p>
71
+ <input />
72
+ </p>
73
+ goodbye.
74
+ <main dangerouslySetInnerHTML={{__html: '<b><img></b>'}} />
75
+ </div>
76
+ );
77
+ },
78
+ });
79
+
80
+ function renderAndQuery(sel) {
81
+ var root = renderMarkupIntoDocument(<section><Component /></section>);
82
+ return sel ? root.querySelector(sel) : root;
83
+ }
84
+
85
+ function renderAndGetInstance(sel) {
86
+ return ReactDOMComponentTree.getInstanceFromNode(renderAndQuery(sel));
87
+ }
88
+
89
+ function renderAndGetClosest(sel) {
90
+ return ReactDOMComponentTree.getClosestInstanceFromNode(
91
+ renderAndQuery(sel)
92
+ );
93
+ }
94
+
95
+ expect(renderAndGetInstance(null)._currentElement.type).toBe('section');
96
+ expect(renderAndGetInstance('div')._currentElement.type).toBe('div');
97
+ expect(renderAndGetInstance('h1')._currentElement.type).toBe('h1');
98
+ expect(renderAndGetInstance('p')._currentElement.type).toBe('p');
99
+ expect(renderAndGetInstance('input')._currentElement.type).toBe('input');
100
+ expect(renderAndGetInstance('main')._currentElement.type).toBe('main');
101
+
102
+ // This one's a text component!
103
+ expect(renderAndGetInstance('span')._stringText).toBe('goodbye.');
104
+
105
+ expect(renderAndGetClosest('b')._currentElement.type).toBe('main');
106
+ expect(renderAndGetClosest('img')._currentElement.type).toBe('main');
107
+ });
108
+
109
});
src/renderers/dom/shared/ReactDOMComponent.js
+10
-16
@@ -82,7 +82,7 @@ if (__DEV__) {
82
props: {
83
enumerable: false,
84
get: function() {
85
- var component = this._reactInternalComponent;
85
+ var component = ReactDOMComponentTree.getInstanceFromNode(this);
86
warning(
87
false,
88
'ReactDOMComponent: Do not access .props of a DOM node; instead, ' +
@@ -99,7 +99,7 @@ if (__DEV__) {
99
100
function legacyGetDOMNode() {
101
if (__DEV__) {
102
- var component = this._reactInternalComponent;
102
+ var component = ReactDOMComponentTree.getInstanceFromNode(this);
103
warning(
104
false,
105
'ReactDOMComponent: Do not access .getDOMNode() of a DOM node; ' +
@@ -111,7 +111,7 @@ function legacyGetDOMNode() {
111
}
112
113
function legacyIsMounted() {
114
- var component = this._reactInternalComponent;
114
+ var component = ReactDOMComponentTree.getInstanceFromNode(this);
115
if (__DEV__) {
116
warning(
117
false,
@@ -124,7 +124,7 @@ function legacyIsMounted() {
124
125
function legacySetStateEtc() {
126
if (__DEV__) {
127
- var component = this._reactInternalComponent;
127
+ var component = ReactDOMComponentTree.getInstanceFromNode(this);
128
warning(
129
false,
130
'ReactDOMComponent: Do not access .setState(), .replaceState(), or ' +
@@ -135,7 +135,7 @@ function legacySetStateEtc() {
135
}
136
137
function legacySetProps(partialProps, callback) {
138
- var component = this._reactInternalComponent;
138
+ var component = ReactDOMComponentTree.getInstanceFromNode(this);
139
if (__DEV__) {
140
warning(
141
false,
@@ -154,7 +154,7 @@ function legacySetProps(partialProps, callback) {
154
}
155
156
function legacyReplaceProps(partialProps, callback) {
157
- var component = this._reactInternalComponent;
157
+ var component = ReactDOMComponentTree.getInstanceFromNode(this);
158
if (__DEV__) {
159
warning(
160
false,
@@ -661,7 +661,7 @@ ReactDOMComponent.Mixin = {
661
this._currentElement.type
662
);
663
}
664
- this._nativeNode = el;
664
+ ReactDOMComponentTree.precacheNode(this, el);
665
this._flags |= Flags.hasCachedChildNodes;
666
DOMPropertyOperations.setAttributeForID(el, this._rootNodeID);
667
// Populate node cache
@@ -1145,11 +1145,7 @@ ReactDOMComponent.Mixin = {
1145
break;
1146
}
1147
1148
- if (this._flags & Flags.nodeHasLegacyProperties) {
1149
- this._nativeNode._reactInternalComponent = null;
1150
- }
1151
- this._nativeNode = null;
1152
-
1148
+ ReactDOMComponentTree.uncacheNode(this);
1149
this.unmountChildren();
1150
EventPluginHub.deleteAllListeners(this._rootNodeID);
1151
ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID);
@@ -1158,12 +1154,10 @@ ReactDOMComponent.Mixin = {
1154
},
1155
1156
getPublicInstance: function() {
1157
+ var node = getNode(this);
1158
if (this._flags & Flags.nodeHasLegacyProperties) {
1162
- return this._nativeNode;
1159
+ return node;
1160
} else {
1164
- var node = getNode(this);
1165
-
1166
- node._reactInternalComponent = this;
1161
node.getDOMNode = legacyGetDOMNode;
1162
node.isMounted = legacyIsMounted;
1163
node.setState = legacySetStateEtc;