Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/docs/ref-02-component-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Instances of a React Component are created internally in React when rendering. T
setState(object nextState[, function callback])
```

Merges nextState with the current state. This is the primary method you use to trigger UI updates from event handlers and server request callbacks. In addition, you can supply an optional callback function that is executed once `setState` is completed and the component is re-rendered.
Merges nextState with the current state. This is the primary method you use to trigger UI updates from event handlers and server request callbacks. In addition, you can supply an optional callback function that is executed once `setState` is completed and the component is re-rendered. If Promises/A+ are supported, the method also returns a promise.

> Notes:
>
Expand Down
10 changes: 5 additions & 5 deletions src/class/ReactClass.js
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,7 @@ var ReactClassMixin = {
internalInstance,
'setState(...): Can only update a mounted or mounting component.'
);
internalInstance.setState(
return internalInstance.setState(
partialState, callback && callback.bind(this)
);
},
Expand All @@ -745,7 +745,7 @@ var ReactClassMixin = {
internalInstance,
'replaceState(...): Can only update a mounted or mounting component.'
);
internalInstance.replaceState(
return internalInstance.replaceState(
newState,
callback && callback.bind(this)
);
Expand All @@ -772,7 +772,7 @@ var ReactClassMixin = {
'forceUpdate(...): Can only force an update on mounted or mounting ' +
'components.'
);
internalInstance.forceUpdate(callback && callback.bind(this));
return internalInstance.forceUpdate(callback && callback.bind(this));
},

/**
Expand Down Expand Up @@ -803,7 +803,7 @@ var ReactClassMixin = {
internalInstance,
'setProps(...): Can only update a mounted component.'
);
internalInstance.setProps(
return internalInstance.setProps(
partialProps,
callback && callback.bind(this)
);
Expand All @@ -819,7 +819,7 @@ var ReactClassMixin = {
* @deprecated
*/
replaceProps: function(newProps, callback) {
ReactInstanceMap.get(this).replaceProps(
return ReactInstanceMap.get(this).replaceProps(
newProps,
callback && callback.bind(this)
);
Expand Down
12 changes: 6 additions & 6 deletions src/core/ReactCompositeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ var ReactCompositeComponentMixin = assign({},
// Merge with the pending element if it exists, otherwise with existing
// element props.
var element = this._pendingElement || this._currentElement;
this.replaceProps(
return this.replaceProps(
assign({}, element.props, partialProps),
callback
);
Expand Down Expand Up @@ -322,7 +322,7 @@ var ReactCompositeComponentMixin = assign({},
this._pendingElement || this._currentElement,
props
);
ReactUpdates.enqueueUpdate(this, callback);
return ReactUpdates.enqueueUpdate(this, callback);
},

/**
Expand All @@ -341,7 +341,7 @@ var ReactCompositeComponentMixin = assign({},
element,
assign({}, element.props, partialProps)
);
ReactUpdates.enqueueUpdate(this, callback);
return ReactUpdates.enqueueUpdate(this, callback);
},

/**
Expand All @@ -357,7 +357,7 @@ var ReactCompositeComponentMixin = assign({},
*/
setState: function(partialState, callback) {
// Merge with `_pendingState` if it exists, otherwise with existing state.
this.replaceState(
return this.replaceState(
assign({}, this._pendingState || this._instance.state, partialState),
callback
);
Expand Down Expand Up @@ -385,7 +385,7 @@ var ReactCompositeComponentMixin = assign({},
// TODO: The callback here is ignored when setState is called from
// componentWillMount. Either fix it or disallow doing so completely in
// favor of getInitialState.
ReactUpdates.enqueueUpdate(this, callback);
return ReactUpdates.enqueueUpdate(this, callback);
}
},

Expand All @@ -412,7 +412,7 @@ var ReactCompositeComponentMixin = assign({},
'or during an existing state transition (such as within `render`).'
);
this._pendingForceUpdate = true;
ReactUpdates.enqueueUpdate(this, callback);
return ReactUpdates.enqueueUpdate(this, callback);
},

/**
Expand Down
21 changes: 18 additions & 3 deletions src/core/ReactUpdates.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,29 @@ function enqueueUpdate(component, callback) {
}

dirtyComponents.push(component);
var resolver, promise;

if (typeof Promise === 'function') {
promise = new Promise(function(resolve, reject) {
resolver = function() {
// Always resolve, there is no reason for Promise to be rejected.
if (typeof callback === 'function') {
callback();
}
resolve();
};
});
}

if (callback) {
if (resolver || callback) {
if (component._pendingCallbacks) {
component._pendingCallbacks.push(callback);
component._pendingCallbacks.push(resolver || callback);
} else {
component._pendingCallbacks = [callback];
component._pendingCallbacks = [resolver || callback];
}
}

return promise;
}

/**
Expand Down
39 changes: 39 additions & 0 deletions src/core/__tests__/ReactUpdates-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,45 @@ describe('ReactUpdates', function() {
expect(updateCount).toBe(2);
});

it('should return a promise if Promises/A+ are supported', function() {
var updateCount = 0;
var Component = React.createClass({
getInitialState: function() {
return {x: 0};
},
componentDidUpdate: function() {
updateCount++;
},
render: function() {
return <div>{this.state.x}</div>;
}
});

var instance = ReactTestUtils.renderIntoDocument(<Component />);
expect(instance.state.x).toBe(0);

ReactUpdates.batchedUpdates(function() {
var promise = instance.setState({x: 1});

if (typeof Promise === 'function') {
expect(promise).toBeDefined();
expect(instance.state.x).toBe(0);
promise.then(function() {
instance.setState({x: 2}).then(function() {
expect(instance.state.x).toBe(2);
expect(updateCount).toBe(2);
});
}).then(function() {
expect(instance.state.x).toBe(1);
expect(updateCount).toBe(1);
});
} else {
// Promises are not supported
expect(promise).toBeUndefined();
}
});
});

it('should batch forceUpdate together', function() {
var shouldUpdateCount = 0;
var updateCount = 0;
Expand Down