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