@samitouri / QOS-React-2 / commits / 6d20556c78

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;