diff --git a/src/browser/ui/React.js b/src/browser/ui/React.js index 19de5a77ea907..c966c2db85db3 100644 --- a/src/browser/ui/React.js +++ b/src/browser/ui/React.js @@ -28,6 +28,7 @@ var ReactMount = require('ReactMount'); var ReactMultiChild = require('ReactMultiChild'); var ReactPerf = require('ReactPerf'); var ReactPropTypes = require('ReactPropTypes'); +var ReactRef = require('ReactRef'); var ReactServerRendering = require('ReactServerRendering'); var ReactTextComponent = require('ReactTextComponent'); @@ -62,6 +63,9 @@ var React = { createClass: ReactClass.createClass, createElement: createElement, createFactory: createFactory, + createRef: function() { + return new ReactRef(); + }, constructAndRenderComponent: ReactMount.constructAndRenderComponent, constructAndRenderComponentByID: ReactMount.constructAndRenderComponentByID, render: render, diff --git a/src/core/ReactComponent.js b/src/core/ReactComponent.js index 9d4e4b7e58944..7a7b9688d1bd7 100644 --- a/src/core/ReactComponent.js +++ b/src/core/ReactComponent.js @@ -13,6 +13,7 @@ var ReactElement = require('ReactElement'); var ReactOwner = require('ReactOwner'); +var ReactRef = require('ReactRef'); var ReactUpdates = require('ReactUpdates'); var assign = require('Object.assign'); @@ -56,6 +57,22 @@ var unmountIDFromEnvironment = null; */ var mountImageIntoNode = null; +function attachRef(ref, component, owner) { + if (ref instanceof ReactRef) { + ReactRef.attachRef(ref, component); + } else { + ReactOwner.addComponentAsRefTo(component, ref, owner); + } +} + +function detachRef(ref, component, owner) { + if (ref instanceof ReactRef) { + ReactRef.detachRef(ref, component); + } else { + ReactOwner.removeComponentAsRefFrom(component, ref, owner); + } +} + /** * Components are the basic units of composition in React. * @@ -255,7 +272,7 @@ var ReactComponent = { var ref = this._currentElement.ref; if (ref != null) { var owner = this._currentElement._owner; - ReactOwner.addComponentAsRefTo(this, ref, owner); + attachRef(ref, this, owner); } this._rootNodeID = rootID; this._lifeCycleState = ComponentLifeCycle.MOUNTED; @@ -280,7 +297,7 @@ var ReactComponent = { ); var ref = this._currentElement.ref; if (ref != null) { - ReactOwner.removeComponentAsRefFrom(this, ref, this._owner); + detachRef(ref, this, this._owner); } unmountIDFromEnvironment(this._rootNodeID); this._rootNodeID = null; @@ -350,17 +367,11 @@ var ReactComponent = { if (nextElement._owner !== prevElement._owner || nextElement.ref !== prevElement.ref) { if (prevElement.ref != null) { - ReactOwner.removeComponentAsRefFrom( - this, prevElement.ref, prevElement._owner - ); + detachRef(prevElement.ref, this, prevElement._owner); } // Correct, even if the owner is the same, and only the ref has changed. if (nextElement.ref != null) { - ReactOwner.addComponentAsRefTo( - this, - nextElement.ref, - nextElement._owner - ); + attachRef(nextElement.ref, this, nextElement._owner); } } }, diff --git a/src/core/ReactRef.js b/src/core/ReactRef.js new file mode 100644 index 0000000000000..4463d54bf0fc2 --- /dev/null +++ b/src/core/ReactRef.js @@ -0,0 +1,103 @@ +/** + * Copyright 2013-2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @providesModule ReactRef + */ + +"use strict"; + +var ReactUpdates = require('ReactUpdates'); + +var accumulate = require('accumulate'); +var assign = require('Object.assign'); +var forEachAccumulated = require('forEachAccumulated'); +var invariant = require('invariant'); + +function ReactRef() { + this._value = null; + this._successCallbacks = null; + this._failureCallbacks = null; +} + +/** + * Call the enqueued success or failure callbacks for a ref, as appropriate. + */ +function dispatchCallbacks() { + /*jshint validthis:true */ + var successCallbacks = this._successCallbacks; + var failureCallbacks = this._failureCallbacks; + this._successCallbacks = null; + this._failureCallbacks = null; + + if (this._value) { + forEachAccumulated(successCallbacks, callSuccess, this); + } else { + forEachAccumulated(failureCallbacks, callFailure); + } +} + +/** + * Call a single success callback, passing the ref's value. + */ +function callSuccess(cb) { + /*jshint validthis:true */ + cb(this._value); +} + +/** + * Call a single failure callback, passing no arguments. + */ +function callFailure(cb) { + cb(); +} + +assign(ReactRef.prototype, { + /** + * Get the value of a ref asynchronously. Accepts a success callback and an + * optional failure callback. If the ref has been rendered, the success + * callback will be called with the component instance; otherwise, the failure + * callback will be executed. + * + * @param {function} success Callback in case of success + * @param {?function} failure Callback in case of failure + */ + then: function(success, failure) { + invariant( + typeof success === 'function', + 'ReactRef.then(...): Must provide a success callback.' + ); + if (this._successCallbacks == null) { + ReactUpdates.asap(dispatchCallbacks, this); + } + this._successCallbacks = accumulate(this._successCallbacks, success); + if (failure) { + this._failureCallbacks = accumulate(this._failureCallbacks, failure); + } + } +}); + +ReactRef.attachRef = function(ref, value) { + ref._value = value; +}; + +ReactRef.detachRef = function(ref, value) { + // Check that `component` is still the current ref because we do not want to + // detach the ref if another component stole it. + if (ref._value === value) { + ref._value = null; + } +}; + +module.exports = ReactRef; diff --git a/src/core/__tests__/ReactComponent-test.js b/src/core/__tests__/ReactComponent-test.js index 3da25a0567dc7..3f4129b72a755 100644 --- a/src/core/__tests__/ReactComponent-test.js +++ b/src/core/__tests__/ReactComponent-test.js @@ -118,6 +118,114 @@ describe('ReactComponent', function() { instance = ReactTestUtils.renderIntoDocument(instance); }); + it('should support new-style refs', function() { + var innerObj = {}, outerObj = {}; + + var Wrapper = React.createClass({ + getObject: function() { + return this.props.object; + }, + render: function() { + return