diff --git a/src/isomorphic/classic/class/ReactClass.js b/src/isomorphic/classic/class/ReactClass.js
index eb9abcd797..5955c72a60 100644
--- a/src/isomorphic/classic/class/ReactClass.js
+++ b/src/isomorphic/classic/class/ReactClass.js
@@ -394,6 +394,7 @@ var RESERVED_SPEC_KEYS = {
statics: function(Constructor, statics) {
mixStaticSpecIntoComponent(Constructor, statics);
},
+ autobind: function() {}, // noop
};
function validateTypeDef(Constructor, typeDef, location) {
@@ -498,7 +499,8 @@ function mixSpecIntoComponent(Constructor, spec) {
var shouldAutoBind =
isFunction &&
!isReactClassMethod &&
- !isAlreadyDefined;
+ !isAlreadyDefined &&
+ spec.autobind !== false;
if (shouldAutoBind) {
if (!proto.__reactAutoBindMap) {
diff --git a/src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js b/src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js
new file mode 100644
index 0000000000..cf410eb4e9
--- /dev/null
+++ b/src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js
@@ -0,0 +1,234 @@
+/**
+ * Copyright 2013-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @emails react-core
+ */
+/*global global:true*/
+'use strict';
+
+var mocks = require('mocks');
+var React = require('React');
+var ReactTestUtils = require('ReactTestUtils');
+var reactComponentExpect = require('reactComponentExpect');
+
+// TODO: Test render and all stock methods.
+describe('autobind optout', function() {
+
+ it('should work with manual binding', function() {
+
+ var mouseDidEnter = mocks.getMockFunction();
+ var mouseDidLeave = mocks.getMockFunction();
+ var mouseDidClick = mocks.getMockFunction();
+
+ var TestBindComponent = React.createClass({
+ autobind: false,
+ getInitialState: function() {
+ return {something: 'hi'};
+ },
+ onMouseEnter: mouseDidEnter,
+ onMouseLeave: mouseDidLeave,
+ onClick: mouseDidClick,
+
+ render: function() {
+ return (
+
+ );
+ },
+ });
+
+ var instance1 = ;
+ var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1);
+ var rendered1 = reactComponentExpect(mountedInstance1)
+ .expectRenderedChild()
+ .instance();
+
+ var instance2 = ;
+ var mountedInstance2 = ReactTestUtils.renderIntoDocument(instance2);
+ var rendered2 = reactComponentExpect(mountedInstance2)
+ .expectRenderedChild()
+ .instance();
+
+ ReactTestUtils.Simulate.click(rendered1);
+ expect(mouseDidClick.mock.instances.length).toBe(1);
+ expect(mouseDidClick.mock.instances[0]).toBe(mountedInstance1);
+
+ ReactTestUtils.Simulate.click(rendered2);
+ expect(mouseDidClick.mock.instances.length).toBe(2);
+ expect(mouseDidClick.mock.instances[1]).toBe(mountedInstance2);
+
+ ReactTestUtils.Simulate.mouseOver(rendered1);
+ expect(mouseDidEnter.mock.instances.length).toBe(1);
+ expect(mouseDidEnter.mock.instances[0]).toBe(mountedInstance1);
+
+ ReactTestUtils.Simulate.mouseOver(rendered2);
+ expect(mouseDidEnter.mock.instances.length).toBe(2);
+ expect(mouseDidEnter.mock.instances[1]).toBe(mountedInstance2);
+
+ ReactTestUtils.Simulate.mouseOut(rendered1);
+ expect(mouseDidLeave.mock.instances.length).toBe(1);
+ expect(mouseDidLeave.mock.instances[0]).toBe(mountedInstance1);
+
+ ReactTestUtils.Simulate.mouseOut(rendered2);
+ expect(mouseDidLeave.mock.instances.length).toBe(2);
+ expect(mouseDidLeave.mock.instances[1]).toBe(mountedInstance2);
+ });
+
+ it('should not hold reference to instance', function() {
+ var mouseDidClick = function () {
+ void this.state.something;
+ };
+
+ var TestBindComponent = React.createClass({
+ autobind: false,
+ getInitialState: function() {
+ return {something: 'hi'};
+ },
+ onClick: mouseDidClick,
+
+ // auto binding only occurs on top level functions in class defs.
+ badIdeas: {
+ badBind: function() {
+ void this.state.something;
+ },
+ },
+
+ render: function() {
+ return (
+
+ );
+ },
+ });
+
+ var instance1 = ;
+ var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1);
+ var rendered1 = reactComponentExpect(mountedInstance1)
+ .expectRenderedChild()
+ .instance();
+
+ var instance2 = ;
+ var mountedInstance2 = ReactTestUtils.renderIntoDocument(instance2);
+ var rendered2 = reactComponentExpect(mountedInstance2)
+ .expectRenderedChild()
+ .instance();
+
+ expect(function() {
+ var badIdea = instance1.badIdeas.badBind;
+ badIdea();
+ }).toThrow();
+
+ expect(mountedInstance1.onClick).toBe(mountedInstance2.onClick);
+
+ expect(function () {
+ ReactTestUtils.Simulate.click(rendered1);
+ }).toThrow();
+
+ expect(function () {
+ ReactTestUtils.Simulate.click(rendered2);
+ }).toThrow();
+ });
+
+ it('works with mixins that have not opted out of autobinding', function() {
+ var mouseDidClick = mocks.getMockFunction();
+
+ var TestMixin = {
+ onClick: mouseDidClick,
+ };
+
+ var TestBindComponent = React.createClass({
+ mixins: [TestMixin],
+
+ render: function() {
+ return ;
+ },
+ });
+
+ var instance1 = ;
+ var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1);
+ var rendered1 = reactComponentExpect(mountedInstance1)
+ .expectRenderedChild()
+ .instance();
+
+ ReactTestUtils.Simulate.click(rendered1);
+ expect(mouseDidClick.mock.instances.length).toBe(1);
+ expect(mouseDidClick.mock.instances[0]).toBe(mountedInstance1);
+ });
+
+ it('works with mixins that have opted out of autobinding', function() {
+ var mouseDidClick = mocks.getMockFunction();
+
+ var TestMixin = {
+ autobind: false,
+ onClick: mouseDidClick,
+ };
+
+ var TestBindComponent = React.createClass({
+ mixins: [TestMixin],
+
+ render: function() {
+ return ;
+ },
+ });
+
+ var instance1 = ;
+ var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1);
+ var rendered1 = reactComponentExpect(mountedInstance1)
+ .expectRenderedChild()
+ .instance();
+
+ ReactTestUtils.Simulate.click(rendered1);
+ expect(mouseDidClick.mock.instances.length).toBe(1);
+ expect(mouseDidClick.mock.instances[0]).toBe(mountedInstance1);
+ });
+
+ it('does not warn if you try to bind to this', function() {
+ spyOn(console, 'error');
+
+ var TestBindComponent = React.createClass({
+ autobind: false,
+ handleClick: function() { },
+ render: function() {
+ return ;
+ },
+ });
+
+ ReactTestUtils.renderIntoDocument();
+
+ expect(console.error.argsForCall.length).toBe(0);
+ });
+
+ it('does not warn if you pass an manually bound method to setState', function() {
+ spyOn(console, 'error');
+
+ var TestBindComponent = React.createClass({
+ autobind: false,
+ getInitialState: function() {
+ return {foo: 1};
+ },
+ componentDidMount: function() {
+ this.setState({foo: 2}, this.handleUpdate.bind(this));
+ },
+ handleUpdate: function() {
+
+ },
+ render: function() {
+ return ;
+ },
+ });
+
+ ReactTestUtils.renderIntoDocument();
+
+ expect(console.error.argsForCall.length).toBe(0);
+ });
+
+});