@samitouri / QOS-React-2 / commits / b1fc5f949d

Allow classes created with React.createClass to opt out of autobinding

Michael Ridgway committed Jul 23, 2015 at 16:41 UTC b1fc5f949d10d75bc566b1be7722227043fe4c44
2 files changed +237 -1
src/isomorphic/classic/class/ReactClass.js
+3 -1
@@ -395,6 +395,7 @@ var RESERVED_SPEC_KEYS = {
395 statics: function(Constructor, statics) {
396 mixStaticSpecIntoComponent(Constructor, statics);
397 },
398 + autobind: function() {}, // noop
399 };
400
401 function validateTypeDef(Constructor, typeDef, location) {
@@ -499,7 +500,8 @@ function mixSpecIntoComponent(Constructor, spec) {
500 var shouldAutoBind =
501 isFunction &&
502 !isReactClassMethod &&
502 - !isAlreadyDefined;
503 + !isAlreadyDefined &&
504 + spec.autobind !== false;
505
506 if (shouldAutoBind) {
507 if (!proto.__reactAutoBindMap) {
src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js new
+234
@@ -0,0 +1,234 @@
1 +/**
2 + * Copyright 2013-2015, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @emails react-core
10 + */
11 +/*global global:true*/
12 +'use strict';
13 +
14 +var mocks = require('mocks');
15 +var React = require('React');
16 +var ReactTestUtils = require('ReactTestUtils');
17 +var reactComponentExpect = require('reactComponentExpect');
18 +
19 +// TODO: Test render and all stock methods.
20 +describe('autobind optout', function() {
21 +
22 + it('should work with manual binding', function() {
23 +
24 + var mouseDidEnter = mocks.getMockFunction();
25 + var mouseDidLeave = mocks.getMockFunction();
26 + var mouseDidClick = mocks.getMockFunction();
27 +
28 + var TestBindComponent = React.createClass({
29 + autobind: false,
30 + getInitialState: function() {
31 + return {something: 'hi'};
32 + },
33 + onMouseEnter: mouseDidEnter,
34 + onMouseLeave: mouseDidLeave,
35 + onClick: mouseDidClick,
36 +
37 + render: function() {
38 + return (
39 + <div
40 + onMouseOver={this.onMouseEnter.bind(this)}
41 + onMouseOut={this.onMouseLeave.bind(this)}
42 + onClick={this.onClick.bind(this)}
43 + />
44 + );
45 + },
46 + });
47 +
48 + var instance1 = <TestBindComponent />;
49 + var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1);
50 + var rendered1 = reactComponentExpect(mountedInstance1)
51 + .expectRenderedChild()
52 + .instance();
53 +
54 + var instance2 = <TestBindComponent />;
55 + var mountedInstance2 = ReactTestUtils.renderIntoDocument(instance2);
56 + var rendered2 = reactComponentExpect(mountedInstance2)
57 + .expectRenderedChild()
58 + .instance();
59 +
60 + ReactTestUtils.Simulate.click(rendered1);
61 + expect(mouseDidClick.mock.instances.length).toBe(1);
62 + expect(mouseDidClick.mock.instances[0]).toBe(mountedInstance1);
63 +
64 + ReactTestUtils.Simulate.click(rendered2);
65 + expect(mouseDidClick.mock.instances.length).toBe(2);
66 + expect(mouseDidClick.mock.instances[1]).toBe(mountedInstance2);
67 +
68 + ReactTestUtils.Simulate.mouseOver(rendered1);
69 + expect(mouseDidEnter.mock.instances.length).toBe(1);
70 + expect(mouseDidEnter.mock.instances[0]).toBe(mountedInstance1);
71 +
72 + ReactTestUtils.Simulate.mouseOver(rendered2);
73 + expect(mouseDidEnter.mock.instances.length).toBe(2);
74 + expect(mouseDidEnter.mock.instances[1]).toBe(mountedInstance2);
75 +
76 + ReactTestUtils.Simulate.mouseOut(rendered1);
77 + expect(mouseDidLeave.mock.instances.length).toBe(1);
78 + expect(mouseDidLeave.mock.instances[0]).toBe(mountedInstance1);
79 +
80 + ReactTestUtils.Simulate.mouseOut(rendered2);
81 + expect(mouseDidLeave.mock.instances.length).toBe(2);
82 + expect(mouseDidLeave.mock.instances[1]).toBe(mountedInstance2);
83 + });
84 +
85 + it('should not hold reference to instance', function() {
86 + var mouseDidClick = function () {
87 + void this.state.something;
88 + };
89 +
90 + var TestBindComponent = React.createClass({
91 + autobind: false,
92 + getInitialState: function() {
93 + return {something: 'hi'};
94 + },
95 + onClick: mouseDidClick,
96 +
97 + // auto binding only occurs on top level functions in class defs.
98 + badIdeas: {
99 + badBind: function() {
100 + void this.state.something;
101 + },
102 + },
103 +
104 + render: function() {
105 + return (
106 + <div
107 + onClick={this.onClick}
108 + />
109 + );
110 + },
111 + });
112 +
113 + var instance1 = <TestBindComponent />;
114 + var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1);
115 + var rendered1 = reactComponentExpect(mountedInstance1)
116 + .expectRenderedChild()
117 + .instance();
118 +
119 + var instance2 = <TestBindComponent />;
120 + var mountedInstance2 = ReactTestUtils.renderIntoDocument(instance2);
121 + var rendered2 = reactComponentExpect(mountedInstance2)
122 + .expectRenderedChild()
123 + .instance();
124 +
125 + expect(function() {
126 + var badIdea = instance1.badIdeas.badBind;
127 + badIdea();
128 + }).toThrow();
129 +
130 + expect(mountedInstance1.onClick).toBe(mountedInstance2.onClick);
131 +
132 + expect(function () {
133 + ReactTestUtils.Simulate.click(rendered1);
134 + }).toThrow();
135 +
136 + expect(function () {
137 + ReactTestUtils.Simulate.click(rendered2);
138 + }).toThrow();
139 + });
140 +
141 + it('works with mixins that have not opted out of autobinding', function() {
142 + var mouseDidClick = mocks.getMockFunction();
143 +
144 + var TestMixin = {
145 + onClick: mouseDidClick,
146 + };
147 +
148 + var TestBindComponent = React.createClass({
149 + mixins: [TestMixin],
150 +
151 + render: function() {
152 + return <div onClick={this.onClick} />;
153 + },
154 + });
155 +
156 + var instance1 = <TestBindComponent />;
157 + var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1);
158 + var rendered1 = reactComponentExpect(mountedInstance1)
159 + .expectRenderedChild()
160 + .instance();
161 +
162 + ReactTestUtils.Simulate.click(rendered1);
163 + expect(mouseDidClick.mock.instances.length).toBe(1);
164 + expect(mouseDidClick.mock.instances[0]).toBe(mountedInstance1);
165 + });
166 +
167 + it('works with mixins that have opted out of autobinding', function() {
168 + var mouseDidClick = mocks.getMockFunction();
169 +
170 + var TestMixin = {
171 + autobind: false,
172 + onClick: mouseDidClick,
173 + };
174 +
175 + var TestBindComponent = React.createClass({
176 + mixins: [TestMixin],
177 +
178 + render: function() {
179 + return <div onClick={this.onClick.bind(this)} />;
180 + },
181 + });
182 +
183 + var instance1 = <TestBindComponent />;
184 + var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1);
185 + var rendered1 = reactComponentExpect(mountedInstance1)
186 + .expectRenderedChild()
187 + .instance();
188 +
189 + ReactTestUtils.Simulate.click(rendered1);
190 + expect(mouseDidClick.mock.instances.length).toBe(1);
191 + expect(mouseDidClick.mock.instances[0]).toBe(mountedInstance1);
192 + });
193 +
194 + it('does not warn if you try to bind to this', function() {
195 + spyOn(console, 'error');
196 +
197 + var TestBindComponent = React.createClass({
198 + autobind: false,
199 + handleClick: function() { },
200 + render: function() {
201 + return <div onClick={this.handleClick.bind(this)} />;
202 + },
203 + });
204 +
205 + ReactTestUtils.renderIntoDocument(<TestBindComponent />);
206 +
207 + expect(console.error.argsForCall.length).toBe(0);
208 + });
209 +
210 + it('does not warn if you pass an manually bound method to setState', function() {
211 + spyOn(console, 'error');
212 +
213 + var TestBindComponent = React.createClass({
214 + autobind: false,
215 + getInitialState: function() {
216 + return {foo: 1};
217 + },
218 + componentDidMount: function() {
219 + this.setState({foo: 2}, this.handleUpdate.bind(this));
220 + },
221 + handleUpdate: function() {
222 +
223 + },
224 + render: function() {
225 + return <div />;
226 + },
227 + });
228 +
229 + ReactTestUtils.renderIntoDocument(<TestBindComponent />);
230 +
231 + expect(console.error.argsForCall.length).toBe(0);
232 + });
233 +
234 +});