From 27c482a2b83464eb89f5cdfed30e63a7b3ce134e Mon Sep 17 00:00:00 2001
From: Sebastian Markbage <sema@fb.com>
Date: Thu, 6 Nov 2014 19:49:33 -0800
Subject: [PATCH] Separate React Composite and Class

ReactClass is now composed by ReactCompositeComponent rather than
inherit from it.

The state transition functions currently use ReactInstanceMap to map an
instance back to an internal representation.

I updated some tests to use public APIs. Other unit tests still reach into
internals but now we can find them using ReactInstanceMap.

I will do more cleanup in follow ups. The purpose of this diff is to
preserve semantics and most of the existing code.

This effectively enables support for ES6 classes. All you would need to
expose is ReactClassMixin.
---
 src/browser/ui/ReactBrowserComponentMixin.js  |   6 +-
 src/browser/ui/ReactMount.js                  |  32 +-
 .../ui/__tests__/ReactDOMComponent-test.js    | 129 +++----
 .../ui/__tests__/ReactRenderDocument-test.js  |   6 +-
 src/class/ReactClass.js                       | 229 +++++++++++-
 src/core/ReactComponent.js                    |  12 +
 src/core/ReactCompositeComponent.js           | 344 +++++++-----------
 src/core/ReactElementValidator.js             |   7 +-
 src/core/ReactInstanceMap.js                  |  47 +++
 src/core/ReactOwner.js                        |  15 +-
 src/core/ReactPropTransferer.js               |   2 +-
 src/core/ReactRef.js                          |   2 +-
 src/core/__tests__/ReactComponent-test.js     |  51 ++-
 .../__tests__/ReactComponentLifeCycle-test.js |  58 ++-
 .../__tests__/ReactCompositeComponent-test.js |  57 +++
 .../ReactCompositeComponentState-test.js      |  15 +-
 src/core/__tests__/ReactElement-test.js       |   2 +-
 .../__tests__/ReactInstanceHandles-test.js    | 108 +++---
 .../ReactMultiChildReconcile-test.js          |   8 +-
 src/core/__tests__/refs-destruction-test.js   |  18 +-
 src/core/instantiateReactComponent.js         |  74 ++--
 src/test/ReactTestUtils.js                    |  31 +-
 src/test/reactComponentExpect.js              |  24 +-
 23 files changed, 828 insertions(+), 449 deletions(-)
 create mode 100644 src/core/ReactInstanceMap.js

diff --git a/src/browser/ui/ReactBrowserComponentMixin.js b/src/browser/ui/ReactBrowserComponentMixin.js
index f48669b4cc80f..2322b73a22da5 100644
--- a/src/browser/ui/ReactBrowserComponentMixin.js
+++ b/src/browser/ui/ReactBrowserComponentMixin.js
@@ -11,7 +11,6 @@
 
 "use strict";
 
-var ReactEmptyComponent = require('ReactEmptyComponent');
 var ReactMount = require('ReactMount');
 
 var invariant = require('invariant');
@@ -29,10 +28,7 @@ var ReactBrowserComponentMixin = {
       this.isMounted(),
       'getDOMNode(): A component must be mounted to have a DOM node.'
     );
-    if (ReactEmptyComponent.isNullComponentID(this._rootNodeID)) {
-      return null;
-    }
-    return ReactMount.getNode(this._rootNodeID);
+    return ReactMount.getNodeFromInstance(this);
   }
 };
 
diff --git a/src/browser/ui/ReactMount.js b/src/browser/ui/ReactMount.js
index 642fe67e5e47e..b62e56080a969 100644
--- a/src/browser/ui/ReactMount.js
+++ b/src/browser/ui/ReactMount.js
@@ -15,7 +15,9 @@ var DOMProperty = require('DOMProperty');
 var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
 var ReactCurrentOwner = require('ReactCurrentOwner');
 var ReactElement = require('ReactElement');
+var ReactEmptyComponent = require('ReactEmptyComponent');
 var ReactInstanceHandles = require('ReactInstanceHandles');
+var ReactInstanceMap = require('ReactInstanceMap');
 var ReactPerf = require('ReactPerf');
 
 var containsNode = require('containsNode');
@@ -125,6 +127,30 @@ function getNode(id) {
   return nodeCache[id];
 }
 
+/**
+ * Finds the node with the supplied public React instance.
+ *
+ * @param {*} instance A public React instance.
+ * @return {?DOMElement} DOM node with the suppled `id`.
+ * @internal
+ */
+function getNodeFromInstance(instance) {
+  // This instance can currently be either a public or private instance since
+  // native nodes are still public.
+  var id = instance._rootNodeID;
+  // TODO: Once these are only public instances, remove this conditional.
+  if (id == null) {
+    id = ReactInstanceMap.get(instance)._rootNodeID;
+  }
+  if (ReactEmptyComponent.isNullComponentID(id)) {
+    return null;
+  }
+  if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) {
+    nodeCache[id] = ReactMount.findReactNodeByID(id);
+  }
+  return nodeCache[id];
+}
+
 /**
  * A node is "valid" if it is contained by a currently mounted container.
  *
@@ -358,7 +384,7 @@ var ReactMount = {
           nextElement,
           container,
           callback
-        );
+        ).getPublicInstance();
       } else {
         ReactMount.unmountComponentAtNode(container);
       }
@@ -374,7 +400,7 @@ var ReactMount = {
       nextElement,
       container,
       shouldReuseMarkup
-    );
+    ).getPublicInstance();
     callback && callback.call(component);
     return component;
   },
@@ -677,6 +703,8 @@ var ReactMount = {
 
   getNode: getNode,
 
+  getNodeFromInstance: getNodeFromInstance,
+
   purgeID: purgeID
 };
 
diff --git a/src/browser/ui/__tests__/ReactDOMComponent-test.js b/src/browser/ui/__tests__/ReactDOMComponent-test.js
index 2d54865dc3b78..1257e11640477 100644
--- a/src/browser/ui/__tests__/ReactDOMComponent-test.js
+++ b/src/browser/ui/__tests__/ReactDOMComponent-test.js
@@ -21,41 +21,39 @@ describe('ReactDOMComponent', function() {
   describe('updateDOM', function() {
     var React;
     var ReactTestUtils;
-    var transaction;
 
     beforeEach(function() {
       React = require('React');
       ReactTestUtils = require('ReactTestUtils');
-
-      var ReactReconcileTransaction = require('ReactReconcileTransaction');
-      transaction = new ReactReconcileTransaction();
     });
 
     it("should handle className", function() {
-      var stub = ReactTestUtils.renderIntoDocument(<div style={{}} />);
-
-      stub.receiveComponent({props: { className: 'foo' }}, transaction);
-      expect(stub.getDOMNode().className).toEqual('foo');
-      stub.receiveComponent({props: { className: 'bar' }}, transaction);
-      expect(stub.getDOMNode().className).toEqual('bar');
-      stub.receiveComponent({props: { className: null }}, transaction);
-      expect(stub.getDOMNode().className).toEqual('');
+      var container = document.createElement('div');
+      React.render(<div style={{}} />, container);
+
+      React.render(<div className={'foo'} />, container);
+      expect(container.firstChild.className).toEqual('foo');
+      React.render(<div className={'bar'} />, container);
+      expect(container.firstChild.className).toEqual('bar');
+      React.render(<div className={null} />, container);
+      expect(container.firstChild.className).toEqual('');
     });
 
     it("should gracefully handle various style value types", function() {
-      var stub = ReactTestUtils.renderIntoDocument(<div style={{}} />);
-      var stubStyle = stub.getDOMNode().style;
+      var container = document.createElement('div');
+      React.render(<div style={{}} />, container);
+      var stubStyle = container.firstChild.style;
 
       // set initial style
       var setup = { display: 'block', left: '1', top: 2, fontFamily: 'Arial' };
-      stub.receiveComponent({props: { style: setup }}, transaction);
+      React.render(<div style={setup} />, container);
       expect(stubStyle.display).toEqual('block');
       expect(stubStyle.left).toEqual('1px');
       expect(stubStyle.fontFamily).toEqual('Arial');
 
       // reset the style to their default state
       var reset = { display: '', left: null, top: false, fontFamily: true };
-      stub.receiveComponent({props: { style: reset }}, transaction);
+      React.render(<div style={reset} />, container);
       expect(stubStyle.display).toEqual('');
       expect(stubStyle.left).toEqual('');
       expect(stubStyle.top).toEqual('');
@@ -64,34 +62,35 @@ describe('ReactDOMComponent', function() {
 
     it("should update styles when mutating style object", function() {
       var styles = { display: 'none', fontFamily: 'Arial', lineHeight: 1.2 };
-      var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);
+      var container = document.createElement('div');
+      React.render(<div style={styles} />, container);
 
-      var stubStyle = stub.getDOMNode().style;
+      var stubStyle = container.firstChild.style;
       stubStyle.display = styles.display;
       stubStyle.fontFamily = styles.fontFamily;
 
       styles.display = 'block';
 
-      stub.receiveComponent({props: { style: styles }}, transaction);
+      React.render(<div style={styles} />, container);
       expect(stubStyle.display).toEqual('block');
       expect(stubStyle.fontFamily).toEqual('Arial');
       expect(stubStyle.lineHeight).toEqual('1.2');
 
       styles.fontFamily = 'Helvetica';
 
-      stub.receiveComponent({props: { style: styles }}, transaction);
+      React.render(<div style={styles} />, container);
       expect(stubStyle.display).toEqual('block');
       expect(stubStyle.fontFamily).toEqual('Helvetica');
       expect(stubStyle.lineHeight).toEqual('1.2');
 
       styles.lineHeight = 0.5;
 
-      stub.receiveComponent({props: { style: styles }}, transaction);
+      React.render(<div style={styles} />, container);
       expect(stubStyle.display).toEqual('block');
       expect(stubStyle.fontFamily).toEqual('Helvetica');
       expect(stubStyle.lineHeight).toEqual('0.5');
 
-      stub.receiveComponent({props: { style: undefined }}, transaction);
+      React.render(<div style={undefined} />, container);
       expect(stubStyle.display).toBe('');
       expect(stubStyle.fontFamily).toBe('');
       expect(stubStyle.lineHeight).toBe('');
@@ -99,92 +98,96 @@ describe('ReactDOMComponent', function() {
 
     it("should update styles if initially null", function() {
       var styles = null;
-      var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);
+      var container = document.createElement('div');
+      React.render(<div style={styles} />, container);
 
-      var stubStyle = stub.getDOMNode().style;
+      var stubStyle = container.firstChild.style;
 
       styles = {display: 'block'};
 
-      stub.receiveComponent({props: { style: styles }}, transaction);
+      React.render(<div style={styles} />, container);
       expect(stubStyle.display).toEqual('block');
     });
 
     it("should remove attributes", function() {
-      var stub = ReactTestUtils.renderIntoDocument(<img height='17' />);
+      var container = document.createElement('div');
+      React.render(<img height='17' />, container);
 
-      expect(stub.getDOMNode().hasAttribute('height')).toBe(true);
-      stub.receiveComponent({props: {}}, transaction);
-      expect(stub.getDOMNode().hasAttribute('height')).toBe(false);
+      expect(container.firstChild.hasAttribute('height')).toBe(true);
+      React.render(<img />, container);
+      expect(container.firstChild.hasAttribute('height')).toBe(false);
     });
 
     it("should remove properties", function() {
-      var stub = ReactTestUtils.renderIntoDocument(<div className='monkey' />);
+      var container = document.createElement('div');
+      React.render(<div className='monkey' />, container);
 
-      expect(stub.getDOMNode().className).toEqual('monkey');
-      stub.receiveComponent({props: {}}, transaction);
-      expect(stub.getDOMNode().className).toEqual('');
+      expect(container.firstChild.className).toEqual('monkey');
+      React.render(<div />, container);
+      expect(container.firstChild.className).toEqual('');
     });
 
     it("should clear a single style prop when changing 'style'", function() {
       var styles = {display: 'none', color: 'red'};
-      var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);
+      var container = document.createElement('div');
+      React.render(<div style={styles} />, container);
 
-      var stubStyle = stub.getDOMNode().style;
+      var stubStyle = container.firstChild.style;
 
       styles = {color: 'green'};
-      stub.receiveComponent({props: { style: styles }}, transaction);
+      React.render(<div style={styles} />, container);
       expect(stubStyle.display).toEqual('');
       expect(stubStyle.color).toEqual('green');
     });
 
     it("should clear all the styles when removing 'style'", function() {
       var styles = {display: 'none', color: 'red'};
-      var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);
+      var container = document.createElement('div');
+      React.render(<div style={styles} />, container);
 
-      var stubStyle = stub.getDOMNode().style;
+      var stubStyle = container.firstChild.style;
 
-      stub.receiveComponent({props: {}}, transaction);
+      React.render(<div />, container);
       expect(stubStyle.display).toEqual('');
       expect(stubStyle.color).toEqual('');
     });
 
     it("should empty element when removing innerHTML", function() {
-      var stub = ReactTestUtils.renderIntoDocument(
-        <div dangerouslySetInnerHTML={{__html: ':)'}} />
-      );
+      var container = document.createElement('div');
+      React.render(<div dangerouslySetInnerHTML={{__html: ':)'}} />, container);
 
-      expect(stub.getDOMNode().innerHTML).toEqual(':)');
-      stub.receiveComponent({props: {}}, transaction);
-      expect(stub.getDOMNode().innerHTML).toEqual('');
+      expect(container.firstChild.innerHTML).toEqual(':)');
+      React.render(<div />, container);
+      expect(container.firstChild.innerHTML).toEqual('');
     });
 
     it("should transition from string content to innerHTML", function() {
-      var stub = ReactTestUtils.renderIntoDocument(
-        <div>hello</div>
-      );
+      var container = document.createElement('div');
+      React.render(<div>hello</div>, container);
 
-      expect(stub.getDOMNode().innerHTML).toEqual('hello');
-      stub.receiveComponent(
-        {props: {dangerouslySetInnerHTML: {__html: 'goodbye'}}},
-        transaction
+      expect(container.firstChild.innerHTML).toEqual('hello');
+      React.render(
+        <div dangerouslySetInnerHTML={{__html: 'goodbye'}} />,
+        container
       );
-      expect(stub.getDOMNode().innerHTML).toEqual('goodbye');
+      expect(container.firstChild.innerHTML).toEqual('goodbye');
     });
 
     it("should transition from innerHTML to string content", function() {
-      var stub = ReactTestUtils.renderIntoDocument(
-        <div dangerouslySetInnerHTML={{__html: 'bonjour'}} />
-      );
+      var container = document.createElement('div');
+      React.render(<div dangerouslySetInnerHTML={{__html: 'bonjour'}} />
+      , container);
 
-      expect(stub.getDOMNode().innerHTML).toEqual('bonjour');
-      stub.receiveComponent({props: {children: 'adieu'}}, transaction);
-      expect(stub.getDOMNode().innerHTML).toEqual('adieu');
+      expect(container.firstChild.innerHTML).toEqual('bonjour');
+      React.render(<div>adieu</div>, container);
+      expect(container.firstChild.innerHTML).toEqual('adieu');
     });
 
     it("should not incur unnecessary DOM mutations", function() {
-      var stub = ReactTestUtils.renderIntoDocument(<div value="" />);
+      var container = document.createElement('div');
+      React.render(<div value="" />, container);
 
-      var node = stub.getDOMNode();
+      var node = container.firstChild;
       var nodeValue = ''; // node.value always returns undefined
       var nodeValueSetter = mocks.getMockFunction();
       Object.defineProperty(node, 'value', {
@@ -196,10 +199,10 @@ describe('ReactDOMComponent', function() {
         })
       });
 
-      stub.receiveComponent({props: {value: ''}}, transaction);
+      React.render(<div value="" />, container);
       expect(nodeValueSetter.mock.calls.length).toBe(0);
 
-      stub.receiveComponent({props: {}}, transaction);
+      React.render(<div />, container);
       expect(nodeValueSetter.mock.calls.length).toBe(1);
     });
   });
diff --git a/src/browser/ui/__tests__/ReactRenderDocument-test.js b/src/browser/ui/__tests__/ReactRenderDocument-test.js
index 7ebeb2a7c0b1f..b0c644c3a9bb0 100644
--- a/src/browser/ui/__tests__/ReactRenderDocument-test.js
+++ b/src/browser/ui/__tests__/ReactRenderDocument-test.js
@@ -14,6 +14,7 @@
 "use strict";
 
 var React;
+var ReactInstanceMap;
 var ReactMount;
 
 var getTestDocument;
@@ -32,6 +33,7 @@ describe('rendering React components at document', function() {
     require('mock-modules').dumpCache();
 
     React = require('React');
+    ReactInstanceMap = require('ReactInstanceMap');
     ReactMount = require('ReactMount');
     getTestDocument = require('getTestDocument');
 
@@ -61,8 +63,10 @@ describe('rendering React components at document', function() {
     var component = React.render(<Root />, testDocument);
     expect(testDocument.body.innerHTML).toBe('Hello world');
 
+    // TODO: This is a bad test. I have no idea what this is testing.
+    // Node IDs is an implementation detail and not part of the public API.
     var componentID = ReactMount.getReactRootID(testDocument);
-    expect(componentID).toBe(component._rootNodeID);
+    expect(componentID).toBe(ReactInstanceMap.get(component)._rootNodeID);
   });
 
   it('should not be able to unmount component from document node', function() {
diff --git a/src/class/ReactClass.js b/src/class/ReactClass.js
index 13ed707fb5e19..74e424eb4f469 100644
--- a/src/class/ReactClass.js
+++ b/src/class/ReactClass.js
@@ -11,8 +11,10 @@
 
 "use strict";
 
-var ReactCompositeComponent = require('ReactCompositeComponent');
 var ReactElement = require('ReactElement');
+var ReactErrorUtils = require('ReactErrorUtils');
+var ReactInstanceMap = require('ReactInstanceMap');
+var ReactPropTransferer = require('ReactPropTransferer');
 var ReactPropTypeLocations = require('ReactPropTypeLocations');
 var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
 
@@ -22,6 +24,7 @@ var keyMirror = require('keyMirror');
 var keyOf = require('keyOf');
 var monitorCodeUse = require('monitorCodeUse');
 var mapObject = require('mapObject');
+var warning = require('warning');
 
 var MIXINS_KEY = keyOf({mixins: null});
 
@@ -398,7 +401,7 @@ function validateMethodOverride(proto, name) {
     null;
 
   // Disallow overriding of base class methods unless explicitly allowed.
-  if (ReactCompositeComponent.Base.prototype.hasOwnProperty(name)) {
+  if (ReactClassMixin.hasOwnProperty(name)) {
     invariant(
       specPolicy === SpecPolicy.OVERRIDE_BASE,
       'ReactClassInterface: You are attempting to override ' +
@@ -621,6 +624,218 @@ function createChainedFunction(one, two) {
   };
 }
 
+/**
+ * Binds a method to the component.
+ *
+ * @param {object} component Component whose method is going to be bound.
+ * @param {function} method Method to be bound.
+ * @return {function} The bound method.
+ */
+function bindAutoBindMethod(component, method) {
+  var boundMethod = method.bind(component);
+  if (__DEV__) {
+    boundMethod.__reactBoundContext = component;
+    boundMethod.__reactBoundMethod = method;
+    boundMethod.__reactBoundArguments = null;
+    var componentName = component.constructor.displayName;
+    var _bind = boundMethod.bind;
+    boundMethod.bind = function(newThis, ...args) {
+      // User is trying to bind() an autobound method; we effectively will
+      // ignore the value of "this" that the user is trying to use, so
+      // let's warn.
+      if (newThis !== component && newThis !== null) {
+        monitorCodeUse('react_bind_warning', { component: componentName });
+        console.warn(
+          'bind(): React component methods may only be bound to the ' +
+          'component instance. See ' + componentName
+        );
+      } else if (!args.length) {
+        monitorCodeUse('react_bind_warning', { component: componentName });
+        console.warn(
+          'bind(): You are binding a component method to the component. ' +
+          'React does this for you automatically in a high-performance ' +
+          'way, so you can safely remove this call. See ' + componentName
+        );
+        return boundMethod;
+      }
+      var reboundMethod = _bind.apply(boundMethod, arguments);
+      reboundMethod.__reactBoundContext = component;
+      reboundMethod.__reactBoundMethod = method;
+      reboundMethod.__reactBoundArguments = args;
+      return reboundMethod;
+    };
+  }
+  return boundMethod;
+}
+
+/**
+ * Binds all auto-bound methods in a component.
+ *
+ * @param {object} component Component whose method is going to be bound.
+ */
+function bindAutoBindMethods(component) {
+  for (var autoBindKey in component.__reactAutoBindMap) {
+    if (component.__reactAutoBindMap.hasOwnProperty(autoBindKey)) {
+      var method = component.__reactAutoBindMap[autoBindKey];
+      component[autoBindKey] = bindAutoBindMethod(
+        component,
+        ReactErrorUtils.guard(
+          method,
+          component.constructor.displayName + '.' + autoBindKey
+        )
+      );
+    }
+  }
+}
+
+/**
+ * @lends {ReactClass.prototype}
+ */
+var ReactClassMixin = {
+
+  /**
+   * Sets a subset of the state. Always use this or `replaceState` to mutate
+   * state. You should treat `this.state` as immutable.
+   *
+   * There is no guarantee that `this.state` will be immediately updated, so
+   * accessing `this.state` after calling this method may return the old value.
+   *
+   * There is no guarantee that calls to `setState` will run synchronously,
+   * as they may eventually be batched together.  You can provide an optional
+   * callback that will be executed when the call to setState is actually
+   * completed.
+   *
+   * @param {object} partialState Next partial state to be merged with state.
+   * @param {?function} callback Called after state is updated.
+   * @final
+   * @protected
+   */
+  setState: function(partialState, callback) {
+    invariant(
+      typeof partialState === 'object' || partialState == null,
+      'setState(...): takes an object of state variables to update.'
+    );
+    if (__DEV__) {
+      warning(
+        partialState != null,
+        'setState(...): You passed an undefined or null state object; ' +
+        'instead, use forceUpdate().'
+      );
+    }
+    var internalInstance = ReactInstanceMap.get(this);
+    invariant(
+      internalInstance,
+      'setState(...): Can only update a mounted or mounting component.'
+    );
+    internalInstance.setState(
+      partialState, callback && callback.bind(this)
+    );
+  },
+
+  /**
+   * TODO: This will be deprecated because state should always keep a consistent
+   * type signature and the only use case for this, is to avoid that.
+   */
+  replaceState: function(newState, callback) {
+    var internalInstance = ReactInstanceMap.get(this);
+    invariant(
+      internalInstance,
+      'replaceState(...): Can only update a mounted or mounting component.'
+    );
+    internalInstance.replaceState(
+      newState,
+      callback && callback.bind(this)
+    );
+  },
+
+  /**
+   * Forces an update. This should only be invoked when it is known with
+   * certainty that we are **not** in a DOM transaction.
+   *
+   * You may want to call this when you know that some deeper aspect of the
+   * component's state has changed but `setState` was not called.
+   *
+   * This will not invoke `shouldUpdateComponent`, but it will invoke
+   * `componentWillUpdate` and `componentDidUpdate`.
+   *
+   * @param {?function} callback Called after update is complete.
+   * @final
+   * @protected
+   */
+  forceUpdate: function(callback) {
+    var internalInstance = ReactInstanceMap.get(this);
+    invariant(
+      internalInstance,
+      'forceUpdate(...): Can only force an update on mounted or mounting ' +
+        'components.'
+    );
+    internalInstance.forceUpdate(callback && callback.bind(this));
+  },
+
+  /**
+   * Checks whether or not this composite component is mounted.
+   * @return {boolean} True if mounted, false otherwise.
+   * @protected
+   * @final
+   */
+  isMounted: function() {
+    var internalInstance = ReactInstanceMap.get(this);
+    // In theory, isMounted is always true if it exists in the map.
+    // TODO: Remove the internal isMounted method.
+    return internalInstance && internalInstance.isMounted();
+  },
+
+  /**
+   * Sets a subset of the props.
+   *
+   * @param {object} partialProps Subset of the next props.
+   * @param {?function} callback Called after props are updated.
+   * @final
+   * @public
+   * @deprecated
+   */
+  setProps: function(partialProps, callback) {
+    var internalInstance = ReactInstanceMap.get(this);
+    invariant(
+      internalInstance,
+      'setProps(...): Can only update a mounted component.'
+    );
+    internalInstance.setProps(
+      partialProps,
+      callback && callback.bind(this)
+    );
+  },
+
+  /**
+   * Replace all the props.
+   *
+   * @param {object} newProps Subset of the next props.
+   * @param {?function} callback Called after props are updated.
+   * @final
+   * @public
+   * @deprecated
+   */
+  replaceProps: function(newProps, callback) {
+    ReactInstanceMap.get(this).replaceProps(
+      newProps,
+      callback && callback.bind(this)
+    );
+  }
+};
+
+var ReactClassBase = function() {};
+assign(
+  ReactClassBase.prototype,
+  ReactPropTransferer.Mixin,
+  ReactClassMixin
+);
+
+/**
+ * Module for creating composite components.
+ *
+ * @class ReactClass
+ * @extends ReactPropTransferer
+ */
 var ReactClass = {
 
   /**
@@ -633,10 +848,14 @@ var ReactClass = {
   createClass: function(spec) {
     var Constructor = function(props) {
       // This constructor is overridden by mocks. The argument is used
-      // by mocks to assert on what gets mounted. This will later be used
-      // by the stand-alone class implementation.
+      // by mocks to assert on what gets mounted.
+
+      // Wire up auto-binding
+      if (this.__reactAutoBindMap) {
+        bindAutoBindMethods(this);
+      }
     };
-    Constructor.prototype = new ReactCompositeComponent.Base();
+    Constructor.prototype = new ReactClassBase();
     Constructor.prototype.constructor = Constructor;
 
     injectedMixins.forEach(
diff --git a/src/core/ReactComponent.js b/src/core/ReactComponent.js
index 7a7b9688d1bd7..2e400d3e201ec 100644
--- a/src/core/ReactComponent.js
+++ b/src/core/ReactComponent.js
@@ -442,6 +442,18 @@ var ReactComponent = {
         return null;
       }
       return owner.refs[ref];
+    },
+
+    /**
+     * Get the publicly accessible representation of this component - i.e. what
+     * is exposed by refs and renderComponent. Can be null for stateless
+     * components.
+     *
+     * @return {?ReactComponent} the actual sibling Component.
+     * @internal
+     */
+    getPublicInstance: function() {
+      return this;
     }
   }
 };
diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js
index 0c1f187e144e2..b78909c6bd126 100644
--- a/src/core/ReactCompositeComponent.js
+++ b/src/core/ReactCompositeComponent.js
@@ -16,37 +16,31 @@ var ReactContext = require('ReactContext');
 var ReactCurrentOwner = require('ReactCurrentOwner');
 var ReactElement = require('ReactElement');
 var ReactEmptyComponent = require('ReactEmptyComponent');
-var ReactErrorUtils = require('ReactErrorUtils');
+var ReactInstanceMap = require('ReactInstanceMap');
 var ReactOwner = require('ReactOwner');
 var ReactPerf = require('ReactPerf');
-var ReactPropTransferer = require('ReactPropTransferer');
 var ReactPropTypeLocations = require('ReactPropTypeLocations');
 var ReactUpdates = require('ReactUpdates');
 
 var assign = require('Object.assign');
-var instantiateReactComponent = require('instantiateReactComponent');
 var invariant = require('invariant');
 var keyMirror = require('keyMirror');
-var monitorCodeUse = require('monitorCodeUse');
 var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
 var warning = require('warning');
 
 function getDeclarationErrorAddendum(component) {
   var owner = component._owner || null;
-  if (owner && owner.constructor && owner.constructor.displayName) {
-    return ' Check the render method of `' + owner.constructor.displayName +
-      '`.';
+  if (owner) {
+    var constructor = owner._instance.constructor;
+    if (constructor && constructor.displayName) {
+      return ' Check the render method of `' + constructor.displayName + '`.';
+    }
   }
   return '';
 }
 
 function validateLifeCycleOnReplaceState(instance) {
   var compositeLifeCycleState = instance._compositeLifeCycleState;
-  invariant(
-    instance.isMounted() ||
-      compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
-    'replaceState(...): Can only update a mounted or mounting component.'
-  );
   invariant(
     ReactCurrentOwner.current == null,
     'replaceState(...): Cannot update during an existing state transition ' +
@@ -106,7 +100,9 @@ var CompositeLifeCycle = keyMirror({
 /**
  * @lends {ReactCompositeComponent.prototype}
  */
-var ReactCompositeComponentMixin = {
+var ReactCompositeComponentMixin = assign({},
+  ReactComponent.Mixin,
+  ReactOwner.Mixin, {
 
   /**
    * Base constructor for all composite component.
@@ -116,18 +112,16 @@ var ReactCompositeComponentMixin = {
    * @internal
    */
   construct: function(element) {
-    // Children can be either an array or more than one argument
-    ReactComponent.Mixin.construct.apply(this, arguments);
-    ReactOwner.Mixin.construct.apply(this, arguments);
+    this._instance.props = element.props;
+    this._instance.state = null;
+    this._instance.context = null;
 
-    this.state = null;
     this._pendingState = null;
-
-    // This is the public post-processed context. The real context and pending
-    // context lives on the element.
-    this.context = null;
-
     this._compositeLifeCycleState = null;
+
+    // Children can be either an array or more than one argument
+    ReactComponent.Mixin.construct.apply(this, arguments);
+    ReactOwner.Mixin.construct.apply(this, arguments);
   },
 
   /**
@@ -161,47 +155,48 @@ var ReactCompositeComponentMixin = {
         transaction,
         mountDepth
       );
-      this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
 
-      if (this.__reactAutoBindMap) {
-        this._bindAutoBindMethods();
-      }
+      var inst = this._instance;
+
+      // Store a reference from the instance back to the internal representation
+      ReactInstanceMap.set(inst, this);
 
-      this.context = this._processContext(this._currentElement._context);
-      this.props = this._processProps(this.props);
+      this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
 
-      this.state = this.getInitialState ? this.getInitialState() : null;
+      inst.context = this._processContext(this._currentElement._context);
+      inst.props = this._processProps(this._currentElement.props);
 
+      var initialState = inst.getInitialState ? inst.getInitialState() : null;
       if (__DEV__) {
         // We allow auto-mocks to proceed as if they're returning null.
-        if (typeof this.state === 'undefined' &&
-            this.getInitialState && this.getInitialState._isMockFunction) {
+        if (typeof initialState === 'undefined' &&
+            inst.getInitialState._isMockFunction) {
           // This is probably bad practice. Consider warning here and
           // deprecating this convenience.
-          this.state = null;
+          initialState = null;
         }
       }
-
       invariant(
-        typeof this.state === 'object' && !Array.isArray(this.state),
+        typeof initialState === 'object' && !Array.isArray(initialState),
         '%s.getInitialState(): must return an object or null',
-        this.constructor.displayName || 'ReactCompositeComponent'
+        inst.constructor.displayName || 'ReactCompositeComponent'
       );
+      inst.state = initialState;
 
       this._pendingState = null;
       this._pendingForceUpdate = false;
 
-      if (this.componentWillMount) {
-        this.componentWillMount();
+      if (inst.componentWillMount) {
+        inst.componentWillMount();
         // When mounting, calls to `setState` by `componentWillMount` will set
         // `this._pendingState` without triggering a re-render.
         if (this._pendingState) {
-          this.state = this._pendingState;
+          inst.state = this._pendingState;
           this._pendingState = null;
         }
       }
 
-      this._renderedComponent = instantiateReactComponent(
+      this._renderedComponent = this._instantiateReactComponent(
         this._renderValidatedComponent(),
         this._currentElement.type // The wrapping type
       );
@@ -213,8 +208,8 @@ var ReactCompositeComponentMixin = {
         transaction,
         mountDepth + 1
       );
-      if (this.componentDidMount) {
-        transaction.getReactMountReady().enqueue(this.componentDidMount, this);
+      if (inst.componentDidMount) {
+        transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
       }
       return markup;
     }
@@ -227,9 +222,11 @@ var ReactCompositeComponentMixin = {
    * @internal
    */
   unmountComponent: function() {
+    var inst = this._instance;
+
     this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING;
-    if (this.componentWillUnmount) {
-      this.componentWillUnmount();
+    if (inst.componentWillUnmount) {
+      inst.componentWillUnmount();
     }
     this._compositeLifeCycleState = null;
 
@@ -238,23 +235,23 @@ var ReactCompositeComponentMixin = {
 
     ReactComponent.Mixin.unmountComponent.call(this);
 
-    // Some existing components rely on this.props even after they've been
+    // Delete the reference from the instance to this internal representation
+    // which allow the internals to be properly cleaned up even if the user
+    // leaks a reference to the public instance.
+    ReactInstanceMap.remove(inst);
+
+    // Some existing components rely on inst.props even after they've been
     // destroyed (in event handlers).
-    // TODO: this.props = null;
-    // TODO: this.state = null;
+    // TODO: inst.props = null;
+    // TODO: inst.state = null;
+    // TODO: inst.context = null;
   },
 
   /**
-   * Sets a subset of the state. Always use this or `replaceState` to mutate
-   * state. You should treat `this.state` as immutable.
-   *
-   * There is no guarantee that `this.state` will be immediately updated, so
-   * accessing `this.state` after calling this method may return the old value.
-   *
-   * There is no guarantee that calls to `setState` will run synchronously,
-   * as they may eventually be batched together.  You can provide an optional
-   * callback that will be executed when the call to setState is actually
-   * completed.
+   * Sets a subset of the state. This only exists because _pendingState is
+   * internal. This provides a merging strategy that is not available to deep
+   * properties which is confusing. TODO: Expose pendingState or don't use it
+   * during the merge.
    *
    * @param {object} partialState Next partial state to be merged with state.
    * @param {?function} callback Called after state is updated.
@@ -262,20 +259,9 @@ var ReactCompositeComponentMixin = {
    * @protected
    */
   setState: function(partialState, callback) {
-    invariant(
-      typeof partialState === 'object' || partialState == null,
-      'setState(...): takes an object of state variables to update.'
-    );
-    if (__DEV__){
-      warning(
-        partialState != null,
-        'setState(...): You passed an undefined or null state object; ' +
-        'instead, use forceUpdate().'
-      );
-    }
     // Merge with `_pendingState` if it exists, otherwise with existing state.
     this.replaceState(
-      assign({}, this._pendingState || this.state, partialState),
+      assign({}, this._pendingState || this._instance.state, partialState),
       callback
     );
   },
@@ -306,6 +292,32 @@ var ReactCompositeComponentMixin = {
     }
   },
 
+  /**
+   * Forces an update. This should only be invoked when it is known with
+   * certainty that we are **not** in a DOM transaction.
+   *
+   * You may want to call this when you know that some deeper aspect of the
+   * component's state has changed but `setState` was not called.
+   *
+   * This will not invoke `shouldUpdateComponent`, but it will invoke
+   * `componentWillUpdate` and `componentDidUpdate`.
+   *
+   * @param {?function} callback Called after update is complete.isM
+   * @final
+   * @protected
+   */
+  forceUpdate: function(callback) {
+    var compositeLifeCycleState = this._compositeLifeCycleState;
+    invariant(
+      compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
+      compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
+      'forceUpdate(...): Cannot force an update while unmounting component ' +
+      'or during an existing state transition (such as within `render`).'
+    );
+    this._pendingForceUpdate = true;
+    ReactUpdates.enqueueUpdate(this, callback);
+  },
+
   /**
    * Filters the context object to only contain keys specified in
    * `contextTypes`, and asserts that they are valid.
@@ -316,7 +328,7 @@ var ReactCompositeComponentMixin = {
    */
   _processContext: function(context) {
     var maskedContext = null;
-    var contextTypes = this.constructor.contextTypes;
+    var contextTypes = this._instance.constructor.contextTypes;
     if (contextTypes) {
       maskedContext = {};
       for (var contextName in contextTypes) {
@@ -339,25 +351,26 @@ var ReactCompositeComponentMixin = {
    * @private
    */
   _processChildContext: function(currentContext) {
-    var childContext = this.getChildContext && this.getChildContext();
-    var displayName = this.constructor.displayName || 'ReactCompositeComponent';
+    var inst = this._instance;
+    var childContext = inst.getChildContext && inst.getChildContext();
+    var displayName = inst.constructor.displayName || 'ReactCompositeComponent';
     if (childContext) {
       invariant(
-        typeof this.constructor.childContextTypes === 'object',
+        typeof inst.constructor.childContextTypes === 'object',
         '%s.getChildContext(): childContextTypes must be defined in order to ' +
         'use getChildContext().',
         displayName
       );
       if (__DEV__) {
         this._checkPropTypes(
-          this.constructor.childContextTypes,
+          inst.constructor.childContextTypes,
           childContext,
           ReactPropTypeLocations.childContext
         );
       }
       for (var name in childContext) {
         invariant(
-          name in this.constructor.childContextTypes,
+          name in inst.constructor.childContextTypes,
           '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
           displayName,
           name
@@ -379,7 +392,8 @@ var ReactCompositeComponentMixin = {
    */
   _processProps: function(newProps) {
     if (__DEV__) {
-      var propTypes = this.constructor.propTypes;
+      var inst = this._instance;
+      var propTypes = inst.constructor.propTypes;
       if (propTypes) {
         this._checkPropTypes(propTypes, newProps, ReactPropTypeLocations.prop);
       }
@@ -398,7 +412,7 @@ var ReactCompositeComponentMixin = {
   _checkPropTypes: function(propTypes, props, location) {
     // TODO: Stop validating prop types here and only use the element
     // validation.
-    var componentName = this.constructor.displayName;
+    var componentName = this._instance.constructor.displayName;
     for (var propName in propTypes) {
       if (propTypes.hasOwnProperty(propName)) {
         var error =
@@ -497,8 +511,10 @@ var ReactCompositeComponentMixin = {
         nextParentElement
       );
 
-      var prevContext = this.context;
-      var prevProps = this.props;
+      var inst = this._instance;
+
+      var prevContext = inst.context;
+      var prevProps = inst.props;
       var nextContext = prevContext;
       var nextProps = prevProps;
       // Distinguish between a props update versus a simple state update
@@ -507,25 +523,25 @@ var ReactCompositeComponentMixin = {
         nextProps = this._processProps(nextParentElement.props);
 
         this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
-        if (this.componentWillReceiveProps) {
-          this.componentWillReceiveProps(nextProps, nextContext);
+        if (inst.componentWillReceiveProps) {
+          inst.componentWillReceiveProps(nextProps, nextContext);
         }
       }
 
       this._compositeLifeCycleState = null;
 
-      var nextState = this._pendingState || this.state;
+      var nextState = this._pendingState || inst.state;
       this._pendingState = null;
 
       var shouldUpdate =
         this._pendingForceUpdate ||
-        !this.shouldComponentUpdate ||
-        this.shouldComponentUpdate(nextProps, nextState, nextContext);
+        !inst.shouldComponentUpdate ||
+        inst.shouldComponentUpdate(nextProps, nextState, nextContext);
 
       if (__DEV__) {
         if (typeof shouldUpdate === "undefined") {
           console.warn(
-            (this.constructor.displayName || 'ReactCompositeComponent') +
+            (inst.constructor.displayName || 'ReactCompositeComponent') +
             '.shouldComponentUpdate(): Returned undefined instead of a ' +
             'boolean value. Make sure to return true or false.'
           );
@@ -536,9 +552,9 @@ var ReactCompositeComponentMixin = {
         // If it's determined that a component should not update, we still want
         // to set props and state but we shortcut the rest of the update.
         this._currentElement = nextParentElement;
-        this.props = nextProps;
-        this.state = nextState;
-        this.context = nextContext;
+        inst.props = nextProps;
+        inst.state = nextState;
+        inst.context = nextContext;
 
         // Owner cannot change because shouldUpdateReactComponent doesn't allow
         // it. TODO: Remove this._owner completely.
@@ -576,18 +592,20 @@ var ReactCompositeComponentMixin = {
     nextContext,
     transaction
   ) {
-    var prevProps = this.props;
-    var prevState = this.state;
-    var prevContext = this.context;
+    var inst = this._instance;
+
+    var prevProps = inst.props;
+    var prevState = inst.state;
+    var prevContext = inst.context;
 
-    if (this.componentWillUpdate) {
-      this.componentWillUpdate(nextProps, nextState, nextContext);
+    if (inst.componentWillUpdate) {
+      inst.componentWillUpdate(nextProps, nextState, nextContext);
     }
 
     this._currentElement = nextElement;
-    this.props = nextProps;
-    this.state = nextState;
-    this.context = nextContext;
+    inst.props = nextProps;
+    inst.state = nextState;
+    inst.context = nextContext;
 
     // Owner cannot change because shouldUpdateReactComponent doesn't allow
     // it. TODO: Remove this._owner completely.
@@ -595,10 +613,10 @@ var ReactCompositeComponentMixin = {
 
     this._updateRenderedComponent(transaction);
 
-    if (this.componentDidUpdate) {
+    if (inst.componentDidUpdate) {
       transaction.getReactMountReady().enqueue(
-        this.componentDidUpdate.bind(this, prevProps, prevState, prevContext),
-        this
+        inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),
+        inst
       );
     }
   },
@@ -623,7 +641,7 @@ var ReactCompositeComponentMixin = {
       var thisID = this._rootNodeID;
       var prevComponentID = prevComponentInstance._rootNodeID;
       prevComponentInstance.unmountComponent();
-      this._renderedComponent = instantiateReactComponent(
+      this._renderedComponent = this._instantiateReactComponent(
         nextRenderedElement,
         this._currentElement.type
       );
@@ -639,38 +657,6 @@ var ReactCompositeComponentMixin = {
     }
   },
 
-  /**
-   * Forces an update. This should only be invoked when it is known with
-   * certainty that we are **not** in a DOM transaction.
-   *
-   * You may want to call this when you know that some deeper aspect of the
-   * component's state has changed but `setState` was not called.
-   *
-   * This will not invoke `shouldUpdateComponent`, but it will invoke
-   * `componentWillUpdate` and `componentDidUpdate`.
-   *
-   * @param {?function} callback Called after update is complete.
-   * @final
-   * @protected
-   */
-  forceUpdate: function(callback) {
-    var compositeLifeCycleState = this._compositeLifeCycleState;
-    invariant(
-      this.isMounted() ||
-        compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
-      'forceUpdate(...): Can only force an update on mounted or mounting ' +
-        'components.'
-    );
-    invariant(
-      compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING &&
-      ReactCurrentOwner.current == null,
-      'forceUpdate(...): Cannot force an update while unmounting component ' +
-      'or within a `render` function.'
-    );
-    this._pendingForceUpdate = true;
-    ReactUpdates.enqueueUpdate(this, callback);
-  },
-
   /**
    * @private
    */
@@ -684,12 +670,13 @@ var ReactCompositeComponentMixin = {
         this._currentElement._context
       );
       ReactCurrentOwner.current = this;
+      var inst = this._instance;
       try {
-        renderedComponent = this.render();
+        renderedComponent = inst.render();
         if (__DEV__) {
           // We allow auto-mocks to proceed as if they're returning null.
           if (typeof renderedComponent === 'undefined' &&
-              this.render._isMockFunction) {
+              inst.render._isMockFunction) {
             // This is probably bad practice. Consider warning here and
             // deprecating this convenience.
             renderedComponent = null;
@@ -709,100 +696,35 @@ var ReactCompositeComponentMixin = {
         ReactElement.isValidElement(renderedComponent),
         '%s.render(): A valid ReactComponent must be returned. You may have ' +
           'returned undefined, an array or some other invalid object.',
-        this.constructor.displayName || 'ReactCompositeComponent'
+        inst.constructor.displayName || 'ReactCompositeComponent'
       );
       return renderedComponent;
     }
   ),
 
   /**
-   * @private
+   * Get the publicly accessible representation of this component - i.e. what
+   * is exposed by refs and renderComponent. Can be null for stateless
+   * components.
+   *
+   * @return {ReactComponent} the public component instance.
+   * @internal
    */
-  _bindAutoBindMethods: function() {
-    for (var autoBindKey in this.__reactAutoBindMap) {
-      if (!this.__reactAutoBindMap.hasOwnProperty(autoBindKey)) {
-        continue;
-      }
-      var method = this.__reactAutoBindMap[autoBindKey];
-      this[autoBindKey] = this._bindAutoBindMethod(ReactErrorUtils.guard(
-        method,
-        this.constructor.displayName + '.' + autoBindKey
-      ));
-    }
+  getPublicInstance: function() {
+    return this._instance;
   },
 
-  /**
-   * Binds a method to the component.
-   *
-   * @param {function} method Method to be bound.
-   * @private
-   */
-  _bindAutoBindMethod: function(method) {
-    var component = this;
-    var boundMethod = method.bind(component);
-    if (__DEV__) {
-      boundMethod.__reactBoundContext = component;
-      boundMethod.__reactBoundMethod = method;
-      boundMethod.__reactBoundArguments = null;
-      var componentName = component.constructor.displayName;
-      var _bind = boundMethod.bind;
-      boundMethod.bind = function(newThis, ...args) {
-        // User is trying to bind() an autobound method; we effectively will
-        // ignore the value of "this" that the user is trying to use, so
-        // let's warn.
-        if (newThis !== component && newThis !== null) {
-          monitorCodeUse('react_bind_warning', { component: componentName });
-          console.warn(
-            'bind(): React component methods may only be bound to the ' +
-            'component instance. See ' + componentName
-          );
-        } else if (!args.length) {
-          monitorCodeUse('react_bind_warning', { component: componentName });
-          console.warn(
-            'bind(): You are binding a component method to the component. ' +
-            'React does this for you automatically in a high-performance ' +
-            'way, so you can safely remove this call. See ' + componentName
-          );
-          return boundMethod;
-        }
-        var reboundMethod = _bind.apply(boundMethod, arguments);
-        reboundMethod.__reactBoundContext = component;
-        reboundMethod.__reactBoundMethod = method;
-        reboundMethod.__reactBoundArguments = args;
-        return reboundMethod;
-      };
-    }
-    return boundMethod;
-  }
-};
+  // Stub
+  _instantiateReactComponent: null
 
-var ReactCompositeComponentBase = function() {};
-assign(
-  ReactCompositeComponentBase.prototype,
-  ReactComponent.Mixin,
-  ReactOwner.Mixin,
-  ReactPropTransferer.Mixin,
-  ReactCompositeComponentMixin
-);
+});
 
-/**
- * Module for creating composite components.
- *
- * @class ReactCompositeComponent
- * @extends ReactComponent
- * @extends ReactOwner
- * @extends ReactPropTransferer
- */
 var ReactCompositeComponent = {
 
   LifeCycle: CompositeLifeCycle,
 
-  Base: ReactCompositeComponentBase
+  Mixin: ReactCompositeComponentMixin
 
 };
 
-// Temporary injection.
-// TODO: Delete this hack once implementation details are hidden.
-instantiateReactComponent._compositeBase = ReactCompositeComponentBase;
-
 module.exports = ReactCompositeComponent;
diff --git a/src/core/ReactElementValidator.js b/src/core/ReactElementValidator.js
index 0469e8ca45fd7..2dacfd5c786a3 100644
--- a/src/core/ReactElementValidator.js
+++ b/src/core/ReactElementValidator.js
@@ -48,7 +48,9 @@ var NUMERIC_PROPERTY_REGEX = /^\d+$/;
  */
 function getCurrentOwnerDisplayName() {
   var current = ReactCurrentOwner.current;
-  return current && current.constructor.displayName || undefined;
+  return (
+    current && current.getPublicInstance().constructor.displayName || undefined
+  );
 }
 
 /**
@@ -128,7 +130,8 @@ function warnAndMonitorForKeyUse(warningID, message, component, parentType) {
       component._owner &&
       component._owner !== ReactCurrentOwner.current) {
     // Name of the component that originally created this child.
-    childOwnerName = component._owner.constructor.displayName;
+    childOwnerName =
+      component._owner.getPublicInstance().constructor.displayName;
 
     message += ` It was passed a child from ${childOwnerName}.`;
   }
diff --git a/src/core/ReactInstanceMap.js b/src/core/ReactInstanceMap.js
new file mode 100644
index 0000000000000..6c6209eb947b6
--- /dev/null
+++ b/src/core/ReactInstanceMap.js
@@ -0,0 +1,47 @@
+/**
+ * Copyright 2013-2014, 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.
+ *
+ * @providesModule ReactInstanceMap
+ */
+
+"use strict";
+
+/**
+ * `ReactInstanceMap` maintains a mapping from a public facing stateful
+ * instance (key) and the internal representation (value). This allows public
+ * methods to accept the user facing instance as an argument and map them back
+ * to internal methods.
+ */
+
+// TODO: Replace this with ES6: var ReactInstanceMap = new Map();
+var ReactInstanceMap = {
+
+  /**
+   * This API should be called `delete` but we'd have to make sure to always
+   * transform these to strings for IE support. When this transform is fully
+   * supported we can rename it.
+   */
+  remove: function(key) {
+    key._reactInternalInstance = undefined;
+  },
+
+  get: function(key) {
+    return key._reactInternalInstance;
+  },
+
+  has: function(key) {
+    return key._reactInternalInstance !== undefined;
+  },
+
+  set: function(key, value) {
+    key._reactInternalInstance = value;
+  }
+
+};
+
+module.exports = ReactInstanceMap;
diff --git a/src/core/ReactOwner.js b/src/core/ReactOwner.js
index 36f155940dec4..22e493493e7d7 100644
--- a/src/core/ReactOwner.js
+++ b/src/core/ReactOwner.js
@@ -100,7 +100,7 @@ var ReactOwner = {
     );
     // Check that `component` is still the current ref because we do not want to
     // detach the ref if another component stole it.
-    if (owner.refs[ref] === component) {
+    if (owner.getPublicInstance().refs[ref] === component.getPublicInstance()) {
       owner.detachRef(ref);
     }
   },
@@ -113,7 +113,8 @@ var ReactOwner = {
   Mixin: {
 
     construct: function() {
-      this.refs = emptyObject;
+      var inst = this.getPublicInstance();
+      inst.refs = emptyObject;
     },
 
     /**
@@ -125,13 +126,16 @@ var ReactOwner = {
      * @private
      */
     attachRef: function(ref, component) {
+      // TODO: Remove this invariant. This is never exposed and cannot be called
+      // by user code. The unit test is already removed.
       invariant(
         component.isOwnedBy(this),
         'attachRef(%s, ...): Only a component\'s owner can store a ref to it.',
         ref
       );
-      var refs = this.refs === emptyObject ? (this.refs = {}) : this.refs;
-      refs[ref] = component;
+      var inst = this.getPublicInstance();
+      var refs = inst.refs === emptyObject ? (inst.refs = {}) : inst.refs;
+      refs[ref] = component.getPublicInstance();
     },
 
     /**
@@ -142,7 +146,8 @@ var ReactOwner = {
      * @private
      */
     detachRef: function(ref) {
-      delete this.refs[ref];
+      var refs = this.getPublicInstance().refs;
+      delete refs[ref];
     }
 
   }
diff --git a/src/core/ReactPropTransferer.js b/src/core/ReactPropTransferer.js
index ad2f93413d677..01a4591725230 100644
--- a/src/core/ReactPropTransferer.js
+++ b/src/core/ReactPropTransferer.js
@@ -129,7 +129,7 @@ var ReactPropTransferer = {
      */
     transferPropsTo: function(element) {
       invariant(
-        element._owner === this,
+        element._owner && element._owner.getPublicInstance() === this,
         '%s: You can\'t call transferPropsTo() on a component that you ' +
         'don\'t own, %s. This usually means you are calling ' +
         'transferPropsTo() on a component passed in as props or children.',
diff --git a/src/core/ReactRef.js b/src/core/ReactRef.js
index 4463d54bf0fc2..b93290703ef3d 100644
--- a/src/core/ReactRef.js
+++ b/src/core/ReactRef.js
@@ -89,7 +89,7 @@ assign(ReactRef.prototype, {
 });
 
 ReactRef.attachRef = function(ref, value) {
-  ref._value = value;
+  ref._value = value.getPublicInstance();
 };
 
 ReactRef.detachRef = function(ref, value) {
diff --git a/src/core/__tests__/ReactComponent-test.js b/src/core/__tests__/ReactComponent-test.js
index 3f4129b72a755..35e7ada062a32 100644
--- a/src/core/__tests__/ReactComponent-test.js
+++ b/src/core/__tests__/ReactComponent-test.js
@@ -12,15 +12,26 @@
 "use strict";
 
 var React;
+var ReactInstanceMap;
 var ReactTestUtils;
 
 var reactComponentExpect;
+var getMountDepth;
 
 describe('ReactComponent', function() {
   beforeEach(function() {
     React = require('React');
+    ReactInstanceMap = require('ReactInstanceMap');
     ReactTestUtils = require('ReactTestUtils');
     reactComponentExpect = require('reactComponentExpect');
+
+    getMountDepth = function(instance) {
+      if (instance.mountComponent) {
+        // Native instance
+        return instance._mountDepth;
+      }
+      return ReactInstanceMap.get(instance)._mountDepth;
+    };
   });
 
   it('should throw on invalid render targets', function() {
@@ -48,26 +59,6 @@ describe('ReactComponent', function() {
     }).toThrow();
   });
 
-  it('should throw when attempting to hijack a ref', function() {
-    var Component = React.createClass({
-      render: function() {
-        var child = this.props.child;
-        this.attachRef('test', child);
-        return child;
-      }
-    });
-
-    var childInstance = ReactTestUtils.renderIntoDocument(<span />);
-    var instance = <Component child={childInstance} />;
-
-    expect(function() {
-      instance = ReactTestUtils.renderIntoDocument(instance);
-    }).toThrow(
-      'Invariant Violation: attachRef(test, ...): Only a component\'s owner ' +
-      'can store a ref to it.'
-    );
-  });
-
   it('should support refs on owned components', function() {
     var innerObj = {}, outerObj = {};
 
@@ -260,8 +251,8 @@ describe('ReactComponent', function() {
 
     var instance = <Owner />;
     instance = ReactTestUtils.renderIntoDocument(instance);
-    expect(instance._mountDepth).toBe(0);
-    expect(instance.refs.child._mountDepth).toBe(1);
+    expect(getMountDepth(instance)).toBe(0);
+    expect(getMountDepth(instance.refs.child)).toBe(1);
   });
 
   it('should know its (complicated) mount depth', function() {
@@ -291,7 +282,7 @@ describe('ReactComponent', function() {
               ref="switcherDiv"
               style={{
                 display: this.state.tabKey === child.key ? '' : 'none'
-            }}>
+              }}>
               {child}
             </div>
           </Box>
@@ -312,12 +303,12 @@ describe('ReactComponent', function() {
     var root = <App />;
     root = ReactTestUtils.renderIntoDocument(root);
 
-    expect(root._mountDepth).toBe(0);
-    expect(root.refs.switcher._mountDepth).toBe(1);
-    expect(root.refs.switcher.refs.box._mountDepth).toBe(2);
-    expect(root.refs.switcher.refs.switcherDiv._mountDepth).toBe(4);
-    expect(root.refs.child._mountDepth).toBe(5);
-    expect(root.refs.switcher.refs.box.refs.boxDiv._mountDepth).toBe(3);
-    expect(root.refs.child.refs.span._mountDepth).toBe(6);
+    expect(getMountDepth(root)).toBe(0);
+    expect(getMountDepth(root.refs.switcher)).toBe(1);
+    expect(getMountDepth(root.refs.switcher.refs.box)).toBe(2);
+    expect(getMountDepth(root.refs.switcher.refs.switcherDiv)).toBe(4);
+    expect(getMountDepth(root.refs.child)).toBe(5);
+    expect(getMountDepth(root.refs.switcher.refs.box.refs.boxDiv)).toBe(3);
+    expect(getMountDepth(root.refs.child.refs.span)).toBe(6);
   });
 });
diff --git a/src/core/__tests__/ReactComponentLifeCycle-test.js b/src/core/__tests__/ReactComponentLifeCycle-test.js
index a625bf2401786..214b18d017206 100644
--- a/src/core/__tests__/ReactComponentLifeCycle-test.js
+++ b/src/core/__tests__/ReactComponentLifeCycle-test.js
@@ -15,9 +15,13 @@ var React;
 var ReactTestUtils;
 var ReactComponent;
 var ReactCompositeComponent;
+var ReactInstanceMap;
 var ComponentLifeCycle;
 var CompositeComponentLifeCycle;
 
+var getCompositeLifeCycle;
+var getLifeCycleState;
+
 var clone = function(o) {
   return JSON.parse(JSON.stringify(o));
 };
@@ -81,6 +85,23 @@ describe('ReactComponentLifeCycle', function() {
     ReactCompositeComponent = require('ReactCompositeComponent');
     ComponentLifeCycle = ReactComponent.LifeCycle;
     CompositeComponentLifeCycle = ReactCompositeComponent.LifeCycle;
+
+    ReactInstanceMap = require('ReactInstanceMap');
+
+    getCompositeLifeCycle = function(instance) {
+      return ReactInstanceMap.get(instance)._compositeLifeCycleState;
+    };
+
+    getLifeCycleState = function(instance) {
+      var internalInstance = ReactInstanceMap.get(instance);
+      if (!internalInstance) {
+        // Once a component is fully unmounted, we cannot actually get to the
+        // internal instance. It's already dereferenced and possibly GC:ed.
+        // So the unmounted life cycle hook doesn't exist anymore.
+        return ComponentLifeCycle.UNMOUNTED;
+      }
+      return internalInstance._lifeCycleState;
+    };
   });
 
   it('should not reuse an instance when it has been unmounted', function() {
@@ -222,25 +243,25 @@ describe('ReactComponentLifeCycle', function() {
         };
         this._testJournal.returnedFromGetInitialState = clone(initState);
         this._testJournal.lifeCycleAtStartOfGetInitialState =
-          this._lifeCycleState;
+          getLifeCycleState(this);
         this._testJournal.compositeLifeCycleAtStartOfGetInitialState =
-          this._compositeLifeCycleState;
+          getCompositeLifeCycle(this);
         return initState;
       },
 
       componentWillMount: function() {
         this._testJournal.stateAtStartOfWillMount = clone(this.state);
         this._testJournal.lifeCycleAtStartOfWillMount =
-          this._lifeCycleState;
+          getLifeCycleState(this);
         this._testJournal.compositeLifeCycleAtStartOfWillMount =
-          this._compositeLifeCycleState;
+          getCompositeLifeCycle(this);
         this.state.hasWillMountCompleted = true;
       },
 
       componentDidMount: function() {
         this._testJournal.stateAtStartOfDidMount = clone(this.state);
         this._testJournal.lifeCycleAtStartOfDidMount =
-          this._lifeCycleState;
+          getLifeCycleState(this);
         this.setState({hasDidMountCompleted: true});
       },
 
@@ -248,12 +269,12 @@ describe('ReactComponentLifeCycle', function() {
         var isInitialRender = !this.state.hasRenderCompleted;
         if (isInitialRender) {
           this._testJournal.stateInInitialRender = clone(this.state);
-          this._testJournal.lifeCycleInInitialRender = this._lifeCycleState;
+          this._testJournal.lifeCycleInInitialRender = getLifeCycleState(this);
           this._testJournal.compositeLifeCycleInInitialRender =
-            this._compositeLifeCycleState;
+            getCompositeLifeCycle(this);
         } else {
           this._testJournal.stateInLaterRender = clone(this.state);
-          this._testJournal.lifeCycleInLaterRender = this._lifeCycleState;
+          this._testJournal.lifeCycleInLaterRender = getLifeCycleState(this);
         }
         // you would *NEVER* do anything like this in real code!
         this.state.hasRenderCompleted = true;
@@ -267,7 +288,7 @@ describe('ReactComponentLifeCycle', function() {
       componentWillUnmount: function() {
         this._testJournal.stateAtStartOfWillUnmount = clone(this.state);
         this._testJournal.lifeCycleAtStartOfWillUnmount =
-          this._lifeCycleState;
+          getLifeCycleState(this);
         this.state.hasWillUnmountCompleted = true;
       }
     });
@@ -275,7 +296,8 @@ describe('ReactComponentLifeCycle', function() {
     // A component that is merely "constructed" (as in "constructor") but not
     // yet initialized, or rendered.
     //
-    var instance = ReactTestUtils.renderIntoDocument(<LifeCycleComponent />);
+    var container = document.createElement('div');
+    var instance = React.render(<LifeCycleComponent />, container);
 
     // getInitialState
     expect(instance._testJournal.returnedFromGetInitialState).toEqual(
@@ -312,7 +334,7 @@ describe('ReactComponentLifeCycle', function() {
       CompositeComponentLifeCycle.MOUNTING
     );
 
-    expect(instance._lifeCycleState).toBe(ComponentLifeCycle.MOUNTED);
+    expect(getLifeCycleState(instance)).toBe(ComponentLifeCycle.MOUNTED);
 
     // Now *update the component*
     instance.forceUpdate();
@@ -324,10 +346,9 @@ describe('ReactComponentLifeCycle', function() {
       ComponentLifeCycle.MOUNTED
     );
 
-    expect(instance._lifeCycleState).toBe(ComponentLifeCycle.MOUNTED);
+    expect(getLifeCycleState(instance)).toBe(ComponentLifeCycle.MOUNTED);
 
-    // Now *unmountComponent*
-    instance.unmountComponent();
+    React.unmountComponentAtNode(container);
 
     expect(instance._testJournal.stateAtStartOfWillUnmount)
       .toEqual(WILL_UNMOUNT_STATE);
@@ -337,7 +358,7 @@ describe('ReactComponentLifeCycle', function() {
     );
 
     // But the current lifecycle of the component is unmounted.
-    expect(instance._lifeCycleState).toBe(ComponentLifeCycle.UNMOUNTED);
+    expect(getLifeCycleState(instance)).toBe(ComponentLifeCycle.UNMOUNTED);
     expect(instance.state).toEqual(POST_WILL_UNMOUNT_STATE);
   });
 
@@ -487,10 +508,11 @@ describe('ReactComponentLifeCycle', function() {
       componentDidUpdate: logger('inner componentDidUpdate'),
       componentWillUnmount: logger('inner componentWillUnmount')
     });
-    var instance;
 
+
+    var container = document.createElement('div');
     log = [];
-    instance = ReactTestUtils.renderIntoDocument(<Outer x={17} />);
+    var instance = React.render(<Outer x={17} />, container);
     expect(log).toEqual([
       'outer componentWillMount',
       'inner componentWillMount',
@@ -512,7 +534,7 @@ describe('ReactComponentLifeCycle', function() {
     ]);
 
     log = [];
-    instance.unmountComponent();
+    React.unmountComponentAtNode(container);
     expect(log).toEqual([
       'outer componentWillUnmount',
       'inner componentWillUnmount'
diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js
index cf94ce86b88c0..fbe792596f189 100644
--- a/src/core/__tests__/ReactCompositeComponent-test.js
+++ b/src/core/__tests__/ReactCompositeComponent-test.js
@@ -623,6 +623,63 @@ describe('ReactCompositeComponent', function() {
     );
   });
 
+  it('should not allow `setState` on unmounted components', function() {
+    var container = document.createElement('div');
+    document.documentElement.appendChild(container);
+
+    var Component = React.createClass({
+      getInitialState: function() {
+        return { value: 0 };
+      },
+      render: function() {
+        return <div />;
+      }
+    });
+
+    var instance = <Component />;
+    expect(instance.setState).not.toBeDefined();
+
+    instance = React.render(instance, container);
+    expect(function() {
+      instance.setState({ value: 1 });
+    }).not.toThrow();
+
+    React.unmountComponentAtNode(container);
+    expect(function() {
+      instance.setState({ value: 2 });
+    }).toThrow(
+      'Invariant Violation: setState(...): Can only update a mounted or ' +
+      'mounting component.'
+    );
+  });
+
+  it('should not allow `setProps` on unmounted components', function() {
+    var container = document.createElement('div');
+    document.documentElement.appendChild(container);
+
+    var Component = React.createClass({
+      render: function() {
+        return <div />;
+      }
+    });
+
+    var instance = <Component />;
+    expect(instance.setProps).not.toBeDefined();
+
+    instance = React.render(instance, container);
+    expect(function() {
+      instance.setProps({ value: 1 });
+    }).not.toThrow();
+
+    React.unmountComponentAtNode(container);
+    expect(function() {
+      instance.setProps({ value: 2 });
+    }).toThrow(
+      'Invariant Violation: setProps(...): Can only update a mounted ' +
+      'component.'
+    );
+  });
+
   it('should cleanup even if render() fatals', function() {
     var BadComponent = React.createClass({
       render: function() {
diff --git a/src/core/__tests__/ReactCompositeComponentState-test.js b/src/core/__tests__/ReactCompositeComponentState-test.js
index 9de786fbac432..10fa09a1bf929 100644
--- a/src/core/__tests__/ReactCompositeComponentState-test.js
+++ b/src/core/__tests__/ReactCompositeComponentState-test.js
@@ -14,6 +14,7 @@
 var mocks = require('mocks');
 
 var React;
+var ReactInstanceMap;
 var ReactTestUtils;
 var reactComponentExpect;
 
@@ -23,6 +24,7 @@ describe('ReactCompositeComponent-state', function() {
 
   beforeEach(function() {
     React = require('React');
+    ReactInstanceMap = require('ReactInstanceMap');
     ReactTestUtils = require('ReactTestUtils');
     reactComponentExpect = require('reactComponentExpect');
 
@@ -31,10 +33,13 @@ describe('ReactCompositeComponent-state', function() {
         if (state) {
           this.props.stateListener(from, state && state.color);
         } else {
+          var internalInstance = ReactInstanceMap.get(this);
+          var pendingState = internalInstance ? internalInstance._pendingState :
+                             null;
           this.props.stateListener(
             from,
             this.state && this.state.color,
-            this._pendingState && this._pendingState.color
+            pendingState && pendingState.color
           );
         }
       },
@@ -99,13 +104,17 @@ describe('ReactCompositeComponent-state', function() {
   });
 
   it('should support setting state', function() {
+    var container = document.createElement('div');
+    document.documentElement.appendChild(container);
+
     var stateListener = mocks.getMockFunction();
     var instance = <TestComponent stateListener={stateListener} />;
-    instance = ReactTestUtils.renderIntoDocument(instance);
+    instance = React.render(instance, container);
     instance.setProps({nextColor: 'green'});
     instance.setFavoriteColor('blue');
     instance.forceUpdate();
-    instance.unmountComponent();
+
+    React.unmountComponentAtNode(container);
 
     expect(stateListener.mock.calls).toEqual([
       // there is no state when getInitialState() is called
diff --git a/src/core/__tests__/ReactElement-test.js b/src/core/__tests__/ReactElement-test.js
index ba5af2e77ab83..76255209ddb1a 100644
--- a/src/core/__tests__/ReactElement-test.js
+++ b/src/core/__tests__/ReactElement-test.js
@@ -138,7 +138,7 @@ describe('ReactElement', function() {
 
     var instance = ReactTestUtils.renderIntoDocument(<Wrapper />);
 
-    expect(element._owner).toBe(instance);
+    expect(element._owner.getPublicInstance()).toBe(instance);
   });
 
   it('merges an additional argument onto the children prop', function() {
diff --git a/src/core/__tests__/ReactInstanceHandles-test.js b/src/core/__tests__/ReactInstanceHandles-test.js
index 82e835282d23f..0620b40d21291 100644
--- a/src/core/__tests__/ReactInstanceHandles-test.js
+++ b/src/core/__tests__/ReactInstanceHandles-test.js
@@ -62,6 +62,14 @@ describe('ReactInstanceHandles', function() {
     });
   }
 
+  function getNodeID(instance) {
+    if (instance === null) {
+      return '';
+    }
+    var internal = ReactTestUtils.getInternalRepresentation(instance);
+    return internal._rootNodeID;
+  }
+
   beforeEach(function() {
     ReactInstanceHandles = require('ReactInstanceHandles');
     aggregatedArgs = [];
@@ -178,17 +186,17 @@ describe('ReactInstanceHandles', function() {
 
     it("should traverse two phase across component boundary", function() {
       var parent = renderParentIntoDocument();
-      var targetID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
+      var targetID = getNodeID(parent.refs.P_P1_C1.refs.DIV_1);
       var expectedAggregation = [
-        {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG},
-        {id: parent.refs.P_P1._rootNodeID, isUp: false, arg: ARG},
-        {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: false, arg: ARG},
-        {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: false, arg: ARG},
-
-        {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG},
-        {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG},
-        {id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG},
-        {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
+        {id: getNodeID(parent.refs.P), isUp: false, arg: ARG},
+        {id: getNodeID(parent.refs.P_P1), isUp: false, arg: ARG},
+        {id: getNodeID(parent.refs.P_P1_C1.refs.DIV), isUp: false, arg: ARG},
+        {id: getNodeID(parent.refs.P_P1_C1.refs.DIV_1), isUp: false, arg: ARG},
+
+        {id: getNodeID(parent.refs.P_P1_C1.refs.DIV_1), isUp: true, arg: ARG},
+        {id: getNodeID(parent.refs.P_P1_C1.refs.DIV), isUp: true, arg: ARG},
+        {id: getNodeID(parent.refs.P_P1), isUp: true, arg: ARG},
+        {id: getNodeID(parent.refs.P), isUp: true, arg: ARG}
       ];
       ReactInstanceHandles.traverseTwoPhase(targetID, argAggregator, ARG);
       expect(aggregatedArgs).toEqual(expectedAggregation);
@@ -196,10 +204,10 @@ describe('ReactInstanceHandles', function() {
 
     it("should traverse two phase at shallowest node", function() {
       var parent = renderParentIntoDocument();
-      var targetID = parent.refs.P._rootNodeID;
+      var targetID = getNodeID(parent.refs.P);
       var expectedAggregation = [
-        {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG},
-        {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
+        {id: getNodeID(parent.refs.P), isUp: false, arg: ARG},
+        {id: getNodeID(parent.refs.P), isUp: true, arg: ARG}
       ];
       ReactInstanceHandles.traverseTwoPhase(targetID, argAggregator, ARG);
       expect(aggregatedArgs).toEqual(expectedAggregation);
@@ -218,8 +226,8 @@ describe('ReactInstanceHandles', function() {
 
     it("should not traverse if enter/leave the same node", function() {
       var parent = renderParentIntoDocument();
-      var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
-      var enterID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
+      var leaveID = getNodeID(parent.refs.P_P1_C1.refs.DIV_1);
+      var enterID = getNodeID(parent.refs.P_P1_C1.refs.DIV_1);
       var expectedAggregation = [];
       ReactInstanceHandles.traverseEnterLeave(
         leaveID, enterID, argAggregator, ARG, ARG2
@@ -229,12 +237,12 @@ describe('ReactInstanceHandles', function() {
 
     it("should traverse enter/leave to sibling - avoids parent", function() {
       var parent = renderParentIntoDocument();
-      var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
-      var enterID = parent.refs.P_P1_C1.refs.DIV_2._rootNodeID;
+      var leaveID = getNodeID(parent.refs.P_P1_C1.refs.DIV_1);
+      var enterID = getNodeID(parent.refs.P_P1_C1.refs.DIV_2);
       var expectedAggregation = [
-        {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG},
+        {id: getNodeID(parent.refs.P_P1_C1.refs.DIV_1), isUp: true, arg: ARG},
         // enter/leave shouldn't fire antyhing on the parent
-        {id: parent.refs.P_P1_C1.refs.DIV_2._rootNodeID, isUp: false, arg: ARG2}
+        {id: getNodeID(parent.refs.P_P1_C1.refs.DIV_2), isUp: false, arg: ARG2}
       ];
       ReactInstanceHandles.traverseEnterLeave(
         leaveID, enterID, argAggregator, ARG, ARG2
@@ -244,10 +252,10 @@ describe('ReactInstanceHandles', function() {
 
     it("should traverse enter/leave to parent - avoids parent", function() {
       var parent = renderParentIntoDocument();
-      var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
-      var enterID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
+      var leaveID = getNodeID(parent.refs.P_P1_C1.refs.DIV_1);
+      var enterID = getNodeID(parent.refs.P_P1_C1.refs.DIV);
       var expectedAggregation = [
-        {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG}
+        {id: getNodeID(parent.refs.P_P1_C1.refs.DIV_1), isUp: true, arg: ARG}
       ];
       ReactInstanceHandles.traverseEnterLeave(
         leaveID, enterID, argAggregator, ARG, ARG2
@@ -258,11 +266,11 @@ describe('ReactInstanceHandles', function() {
     it("should enter from the window", function() {
       var parent = renderParentIntoDocument();
       var leaveID = ''; // From the window or outside of the React sandbox.
-      var enterID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
+      var enterID = getNodeID(parent.refs.P_P1_C1.refs.DIV);
       var expectedAggregation = [
-        {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG2},
-        {id: parent.refs.P_P1._rootNodeID, isUp: false, arg: ARG2},
-        {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: false, arg: ARG2}
+        {id: getNodeID(parent.refs.P), isUp: false, arg: ARG2},
+        {id: getNodeID(parent.refs.P_P1), isUp: false, arg: ARG2},
+        {id: getNodeID(parent.refs.P_P1_C1.refs.DIV), isUp: false, arg: ARG2}
       ];
       ReactInstanceHandles.traverseEnterLeave(
         leaveID, enterID, argAggregator, ARG, ARG2
@@ -273,9 +281,9 @@ describe('ReactInstanceHandles', function() {
     it("should enter from the window to the shallowest", function() {
       var parent = renderParentIntoDocument();
       var leaveID = ''; // From the window or outside of the React sandbox.
-      var enterID = parent.refs.P._rootNodeID;
+      var enterID = getNodeID(parent.refs.P);
       var expectedAggregation = [
-        {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG2}
+        {id: getNodeID(parent.refs.P), isUp: false, arg: ARG2}
       ];
       ReactInstanceHandles.traverseEnterLeave(
         leaveID, enterID, argAggregator, ARG, ARG2
@@ -286,11 +294,11 @@ describe('ReactInstanceHandles', function() {
     it("should leave to the window", function() {
       var parent = renderParentIntoDocument();
       var enterID = ''; // From the window or outside of the React sandbox.
-      var leaveID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
+      var leaveID = getNodeID(parent.refs.P_P1_C1.refs.DIV);
       var expectedAggregation = [
-        {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG},
-        {id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG},
-        {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
+        {id: getNodeID(parent.refs.P_P1_C1.refs.DIV), isUp: true, arg: ARG},
+        {id: getNodeID(parent.refs.P_P1), isUp: true, arg: ARG},
+        {id: getNodeID(parent.refs.P), isUp: true, arg: ARG}
       ];
       ReactInstanceHandles.traverseEnterLeave(
         leaveID, enterID, argAggregator, ARG, ARG2
@@ -301,11 +309,11 @@ describe('ReactInstanceHandles', function() {
     it("should leave to the window from the shallowest", function() {
       var parent = renderParentIntoDocument();
       var enterID = ''; // From the window or outside of the React sandbox.
-      var leaveID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
+      var leaveID = getNodeID(parent.refs.P_P1_C1.refs.DIV);
       var expectedAggregation = [
-        {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG},
-        {id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG},
-        {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
+        {id: getNodeID(parent.refs.P_P1_C1.refs.DIV), isUp: true, arg: ARG},
+        {id: getNodeID(parent.refs.P_P1), isUp: true, arg: ARG},
+        {id: getNodeID(parent.refs.P), isUp: true, arg: ARG}
       ];
       ReactInstanceHandles.traverseEnterLeave(
         leaveID, enterID, argAggregator, ARG, ARG2
@@ -320,9 +328,9 @@ describe('ReactInstanceHandles', function() {
       expect(
         ReactInstanceHandles._getNextDescendantID(
           '',
-          parent.refs.P_P1._rootNodeID
+          getNodeID(parent.refs.P_P1)
         )
-      ).toBe(parent.refs.P._rootNodeID);
+      ).toBe(getNodeID(parent.refs.P));
     });
 
     it("should return window for next descendent towards window", function() {
@@ -333,10 +341,10 @@ describe('ReactInstanceHandles', function() {
       var parent = renderParentIntoDocument();
       expect(
         ReactInstanceHandles._getNextDescendantID(
-          parent.refs.P_P1._rootNodeID,
-          parent.refs.P_P1._rootNodeID
+          getNodeID(parent.refs.P_P1),
+          getNodeID(parent.refs.P_P1)
         )
-      ).toBe(parent.refs.P_P1._rootNodeID);
+      ).toBe(getNodeID(parent.refs.P_P1));
     });
   });
 
@@ -345,19 +353,19 @@ describe('ReactInstanceHandles', function() {
       var parent = renderParentIntoDocument();
       var ancestors = [
         // Common ancestor from window to deep element is ''.
-        { one: {_rootNodeID: ''},
+        { one: null,
           two: parent.refs.P_P1_C1.refs.DIV_1,
-          com: {_rootNodeID: ''}
+          com: null
         },
         // Same as previous - reversed direction.
         { one: parent.refs.P_P1_C1.refs.DIV_1,
-          two: {_rootNodeID: ''},
-          com: {_rootNodeID: ''}
+          two: null,
+          com: null
         },
         // Common ancestor from window to shallow id is ''.
         { one: parent.refs.P,
-          two: {_rootNodeID: ''},
-          com: {_rootNodeID: ''}
+          two: null,
+          com: null
         },
         // Common ancestor with self is self.
         { one: parent.refs.P_P1_C1.refs.DIV_1,
@@ -401,10 +409,10 @@ describe('ReactInstanceHandles', function() {
       for (i = 0; i < ancestors.length; i++) {
         var plan = ancestors[i];
         var firstCommon = ReactInstanceHandles._getFirstCommonAncestorID(
-          plan.one._rootNodeID,
-          plan.two._rootNodeID
+          getNodeID(plan.one),
+          getNodeID(plan.two)
         );
-        expect(firstCommon).toBe(plan.com._rootNodeID);
+        expect(firstCommon).toBe(getNodeID(plan.com));
       }
     });
   });
diff --git a/src/core/__tests__/ReactMultiChildReconcile-test.js b/src/core/__tests__/ReactMultiChildReconcile-test.js
index babe8616c2db7..faa9d467efa31 100644
--- a/src/core/__tests__/ReactMultiChildReconcile-test.js
+++ b/src/core/__tests__/ReactMultiChildReconcile-test.js
@@ -14,6 +14,7 @@
 require('mock-modules');
 
 var React = require('React');
+var ReactInstanceMap = require('ReactInstanceMap');
 var ReactTestUtils = require('ReactTestUtils');
 var ReactMount = require('ReactMount');
 
@@ -83,7 +84,10 @@ var FriendsStatusDisplay = React.createClass({
   getStatusDisplays: function() {
     var name;
     var orderOfUsernames = [];
-    var statusDisplays = this._renderedComponent._renderedChildren;
+    // TODO: Update this to a better test that doesn't rely so much on internal
+    // implementation details.
+    var statusDisplays =
+      ReactInstanceMap.get(this)._renderedComponent._renderedChildren;
     for (name in statusDisplays) {
       var child = statusDisplays[name];
       var isPresent = !!child;
@@ -195,7 +199,7 @@ function verifyDomOrderingAccurate(parentInstance, statusDisplays) {
       continue;
     }
     var statusDisplay = statusDisplays[username];
-    orderedLogicalIds.push(statusDisplay._rootNodeID);
+    orderedLogicalIds.push(ReactInstanceMap.get(statusDisplay)._rootNodeID);
   }
   expect(orderedDomIds).toEqual(orderedLogicalIds);
 }
diff --git a/src/core/__tests__/refs-destruction-test.js b/src/core/__tests__/refs-destruction-test.js
index 4bc95c5a20924..fc1ff60108d45 100644
--- a/src/core/__tests__/refs-destruction-test.js
+++ b/src/core/__tests__/refs-destruction-test.js
@@ -19,9 +19,11 @@ var TestComponent = React.createClass({
   render: function() {
     return (
       <div>
-        <div ref="theInnerDiv">
-          Lets try to destroy this.
-        </div>
+        {this.props.destroy ? null :
+          <div ref="theInnerDiv">
+            Lets try to destroy this.
+          </div>
+        }
       </div>
     );
   }
@@ -33,20 +35,22 @@ describe('refs-destruction', function() {
   });
 
   it("should remove refs when destroying the parent", function() {
-    var testInstance = ReactTestUtils.renderIntoDocument(<TestComponent />);
+    var container = document.createElement('div');
+    var testInstance = React.render(<TestComponent />, container);
     reactComponentExpect(testInstance.refs.theInnerDiv)
         .toBeDOMComponentWithTag('div');
     expect(Object.keys(testInstance.refs || {}).length).toEqual(1);
-    testInstance.unmountComponent();
+    React.unmountComponentAtNode(container);
     expect(Object.keys(testInstance.refs || {}).length).toEqual(0);
   });
 
   it("should remove refs when destroying the child", function() {
-    var testInstance = ReactTestUtils.renderIntoDocument(<TestComponent />);
+    var container = document.createElement('div');
+    var testInstance = React.render(<TestComponent />, container);
     reactComponentExpect(testInstance.refs.theInnerDiv)
         .toBeDOMComponentWithTag('div');
     expect(Object.keys(testInstance.refs || {}).length).toEqual(1);
-    testInstance.refs.theInnerDiv.unmountComponent();
+    React.render(<TestComponent destroy={true} />, container);
     expect(Object.keys(testInstance.refs || {}).length).toEqual(0);
   });
 });
diff --git a/src/core/instantiateReactComponent.js b/src/core/instantiateReactComponent.js
index 3db5ba3a88a76..e342043ac9e02 100644
--- a/src/core/instantiateReactComponent.js
+++ b/src/core/instantiateReactComponent.js
@@ -12,30 +12,37 @@
 
 "use strict";
 
-var warning = require('warning');
-
+var ReactCompositeComponent = require('ReactCompositeComponent');
 var ReactNativeComponent = require('ReactNativeComponent');
 
-// This is temporary until we've hidden all the implementation details
-// TODO: Delete this hack once implementation details are hidden
-var publicAPIs = {
-  forceUpdate: true,
-  replaceState: true,
-  setProps: true,
-  setState: true,
-  getDOMNode: true
-  // Public APIs used internally:
-  // isMounted: true,
-  // replaceProps: true,
-};
+var assign = require('Object.assign');
+var warning = require('warning');
 
-function unmockImplementationDetails(mockInstance) {
-  var ReactCompositeComponentBase = instantiateReactComponent._compositeBase;
-  for (var key in ReactCompositeComponentBase.prototype) {
-    if (!publicAPIs.hasOwnProperty(key)) {
-      mockInstance[key] = ReactCompositeComponentBase.prototype[key];
-    }
+// To avoid a cyclic dependency, we create the final class in this module
+var ReactCompositeComponentWrapper = function(inst) {
+  this._instance = inst;
+};
+assign(
+  ReactCompositeComponentWrapper.prototype,
+  ReactCompositeComponent.Mixin,
+  {
+    _instantiateReactComponent: instantiateReactComponent
   }
+);
+
+/**
+ * Check if the type reference is a known internal type. I.e. not a user
+ * provided composite type.
+ *
+ * @param {function} type
+ * @return {boolean} Returns true if this is a valid internal type.
+ */
+function isInternalComponentType(type) {
+  return (
+    typeof type === 'function' &&
+    typeof type.prototype.mountComponent === 'function' &&
+    typeof type.prototype.receiveComponent === 'function'
+  );
 }
 
 /**
@@ -52,7 +59,7 @@ function instantiateReactComponent(element, parentCompositeType) {
   if (__DEV__) {
     warning(
       element && (typeof element.type === 'function' ||
-                     typeof element.type === 'string'),
+                  typeof element.type === 'string'),
       'Only functions or strings can be mounted as React components.'
     );
   }
@@ -64,17 +71,25 @@ function instantiateReactComponent(element, parentCompositeType) {
       element.props,
       parentCompositeType
     );
+    // If the injected special class is not an internal class, but another
+    // composite, then we must wrap it.
+    // TODO: Move this resolution around to something cleaner.
+    if (typeof instance.mountComponent !== 'function') {
+      instance = new ReactCompositeComponentWrapper(instance);
+    }
+  } else if (isInternalComponentType(element.type)) {
+    // This is temporarily available for custom components that are not string
+    // represenations. I.e. ART. Once those are updated to use the string
+    // representation, we can drop this code path.
+    instance = new element.type(element);
   } else {
-    // Normal case for non-mocks and non-strings
-    instance = new element.type(element.props);
+    // TODO: Update to follow new ES6 initialization. Ideally, we can use props
+    // in property initializers.
+    var inst = new element.type(element.props);
+    instance = new ReactCompositeComponentWrapper(inst);
   }
 
   if (__DEV__) {
-    if (element.type._isMockFunction) {
-      // TODO: Remove this special case
-      unmockImplementationDetails(instance);
-    }
-
     warning(
       typeof instance.construct === 'function' &&
       typeof instance.mountComponent === 'function' &&
@@ -83,8 +98,7 @@ function instantiateReactComponent(element, parentCompositeType) {
     );
   }
 
-  // This actually sets up the internal instance. This will become decoupled
-  // from the public instance in a future diff.
+  // Sets up the instance. This can probably just move into the constructor now.
   instance.construct(element);
 
   return instance;
diff --git a/src/test/ReactTestUtils.js b/src/test/ReactTestUtils.js
index 3e2deccc86adf..bbc2724387f78 100644
--- a/src/test/ReactTestUtils.js
+++ b/src/test/ReactTestUtils.js
@@ -17,6 +17,7 @@ var EventPropagators = require('EventPropagators');
 var React = require('React');
 var ReactElement = require('ReactElement');
 var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
+var ReactInstanceMap = require('ReactInstanceMap');
 var ReactMount = require('ReactMount');
 var ReactTextComponent = require('ReactTextComponent');
 var ReactUpdates = require('ReactUpdates');
@@ -60,7 +61,9 @@ var ReactTestUtils = {
   },
 
   isDOMComponent: function(inst) {
-    return !!(inst && inst.mountComponent && inst.tagName);
+    // TODO: Fix this heuristic. It's just here because composites can currently
+    // pretend to be DOM components.
+    return !!(inst && inst.getDOMNode && inst.tagName);
   },
 
   isDOMComponentElement: function(inst) {
@@ -101,6 +104,22 @@ var ReactTestUtils = {
     return inst instanceof ReactTextComponent.type;
   },
 
+  getInternalRepresentation: function(inst) {
+    // TODO: Remove this duck check once we have a separate DOM/Native instance
+    if (typeof inst.mountComponent === 'function') {
+      return inst;
+    }
+    return ReactInstanceMap.get(inst);
+  },
+
+  getRenderedChildOfCompositeComponent: function(inst) {
+    if (!ReactTestUtils.isCompositeComponent(inst)) {
+      return null;
+    }
+    var internalInstance = ReactInstanceMap.get(inst);
+    return internalInstance._renderedComponent.getPublicInstance();
+  },
+
   findAllInRenderedTree: function(inst, test) {
     if (!inst) {
       return [];
@@ -114,12 +133,18 @@ var ReactTestUtils = {
           continue;
         }
         ret = ret.concat(
-          ReactTestUtils.findAllInRenderedTree(renderedChildren[key], test)
+          ReactTestUtils.findAllInRenderedTree(
+            renderedChildren[key].getPublicInstance(),
+            test
+          )
         );
       }
     } else if (ReactTestUtils.isCompositeComponent(inst)) {
       ret = ret.concat(
-        ReactTestUtils.findAllInRenderedTree(inst._renderedComponent, test)
+        ReactTestUtils.findAllInRenderedTree(
+          ReactTestUtils.getRenderedChildOfCompositeComponent(inst),
+          test
+        )
       );
     }
     return ret;
diff --git a/src/test/reactComponentExpect.js b/src/test/reactComponentExpect.js
index cb03a48b3a058..17c1c7e1afe0c 100644
--- a/src/test/reactComponentExpect.js
+++ b/src/test/reactComponentExpect.js
@@ -12,6 +12,7 @@
 
 "use strict";
 
+var ReactInstanceMap = require('ReactInstanceMap');
 var ReactTestUtils = require('ReactTestUtils');
 
 var assign = require('Object.assign');
@@ -25,7 +26,10 @@ function reactComponentExpect(instance) {
     return new reactComponentExpect(instance);
   }
 
-  this._instance = instance;
+  expect(instance).not.toBeNull();
+
+  this._instance = ReactTestUtils.getInternalRepresentation(instance);
+
   expect(typeof instance).toBe('object');
   expect(typeof instance.constructor).toBe('function');
   expect(ReactTestUtils.isElement(instance)).toBe(false);
@@ -38,7 +42,7 @@ assign(reactComponentExpect.prototype, {
    * @instance: Retrieves the backing instance.
    */
   instance: function() {
-    return this._instance;
+    return this._instance.getPublicInstance();
   },
 
   /**
@@ -57,7 +61,8 @@ assign(reactComponentExpect.prototype, {
    */
   expectRenderedChild: function() {
     this.toBeCompositeComponent();
-    return new reactComponentExpect(this.instance()._renderedComponent);
+    var child = this._instance._renderedComponent;
+    return new reactComponentExpect(child);
   },
 
   /**
@@ -67,7 +72,7 @@ assign(reactComponentExpect.prototype, {
     // Currently only dom components have arrays of children, but that will
     // change soon.
     this.toBeDOMComponent();
-    var renderedChildren = this.instance()._renderedChildren || {};
+    var renderedChildren = this._instance._renderedChildren || {};
     for (var name in renderedChildren) {
       if (!renderedChildren.hasOwnProperty(name)) {
         continue;
@@ -83,15 +88,15 @@ assign(reactComponentExpect.prototype, {
 
   toBeDOMComponentWithChildCount: function(n) {
     this.toBeDOMComponent();
-    expect(this.instance()._renderedChildren).toBeTruthy();
-    var len = Object.keys(this.instance()._renderedChildren).length;
+    expect(this._instance._renderedChildren).toBeTruthy();
+    var len = Object.keys(this._instance._renderedChildren).length;
     expect(len).toBe(n);
     return this;
   },
 
   toBeDOMComponentWithNoChildren: function() {
     this.toBeDOMComponent();
-    expect(this.instance()._renderedChildren).toBeFalsy();
+    expect(this._instance._renderedChildren).toBeFalsy();
     return this;
   },
 
@@ -99,7 +104,7 @@ assign(reactComponentExpect.prototype, {
 
   toBeComponentOfType: function(constructor) {
     expect(
-      this.instance()._currentElement.type === constructor
+      this._instance._currentElement.type === constructor
     ).toBe(true);
     return this;
   },
@@ -110,6 +115,7 @@ assign(reactComponentExpect.prototype, {
    */
   toBeCompositeComponent: function() {
     expect(
+      typeof this.instance() === 'object' &&
       typeof this.instance().render === 'function' &&
       typeof this.instance().setState === 'function'
     ).toBe(true);
@@ -119,7 +125,7 @@ assign(reactComponentExpect.prototype, {
   toBeCompositeComponentWithType: function(constructor) {
     this.toBeCompositeComponent();
     expect(
-      this.instance()._currentElement.type === constructor
+      this._instance._currentElement.type === constructor
     ).toBe(true);
     return this;
   },