Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a new lifecycle method componentWillReceiveContext() #5776

Closed
wants to merge 1 commit into from
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
8 changes: 8 additions & 0 deletions docs/docs/12-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,14 @@ void componentDidUpdate(
)
```

Additionally, a new lifecycle method named `componentWillReceiveContext` is included, which is called after `componentWillReceiveProps`, but before the update occurs. This method will *always* be called if contexts are enabled, unlike `componentWillReceiveProps`, which is only called if props change.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no guarantee that componentWillReceiveProps is called only when props change. The only guarantee is that componentWillReceiveProps will be called if props change.


```javascript
void componentWillReceiveContext(
object nextContext
)
```

## Referencing context in stateless functional components

Stateless functional components are also able to reference `context` if `contextTypes` is defined as a property of the function. The following code shows the `Button` component above written as a stateless functional component.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ describe('ReactContextValidator', function() {

it('should filter context properly in callbacks', function() {
var actualComponentWillReceiveProps;
var actualComponentWillReceiveContext;
var actualShouldComponentUpdate;
var actualComponentWillUpdate;
var actualComponentDidUpdate;
Expand Down Expand Up @@ -105,6 +106,11 @@ describe('ReactContextValidator', function() {
return true;
},

componentWillReceiveContext: function(nextContext) {
actualComponentWillReceiveContext = nextContext;
return true;
},

shouldComponentUpdate: function(nextProps, nextState, nextContext) {
actualShouldComponentUpdate = nextContext;
return true;
Expand All @@ -127,6 +133,7 @@ describe('ReactContextValidator', function() {
ReactDOM.render(<Parent foo="abc" />, container);
ReactDOM.render(<Parent foo="def" />, container);
expect(actualComponentWillReceiveProps).toEqual({foo: 'def'});
expect(actualComponentWillReceiveContext).toEqual({foo: 'def'});
expect(actualShouldComponentUpdate).toEqual({foo: 'def'});
expect(actualComponentWillUpdate).toEqual({foo: 'def'});
expect(actualComponentDidUpdate).toEqual({foo: 'abc'});
Expand Down
18 changes: 18 additions & 0 deletions src/isomorphic/classic/class/ReactClass.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,18 @@ var ReactClassInterface = {
*/
componentWillReceiveProps: SpecPolicy.DEFINE_MANY,

/**
* Invoked before the component receives new context.
*
* This method is analogous to `componentWillReceiveProps`,
* but will only receive the new and changed context. This method exists
* for situations where the former does not trigger, as props have not changed.
*
* @param {object} nextContext
* @optional
*/
componentWillReceiveContext: SpecPolicy.DEFINE_MANY,

/**
* Invoked while deciding if the component should be updated as a result of
* receiving new props, state and/or context.
Expand Down Expand Up @@ -840,6 +852,12 @@ var ReactClass = {
'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
spec.displayName || 'A component'
);
warning(
!Constructor.prototype.componentWillRecieveContext,
'%s has a method called ' +
'componentWillRecieveContext(). Did you mean componentWillReceiveContext()?',
spec.displayName || 'A component'
);
}

// Reduce time spent doing lookups by setting these on the prototype.
Expand Down
16 changes: 16 additions & 0 deletions src/isomorphic/classic/class/__tests__/ReactClass-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,22 @@ describe('ReactClass-spec', function() {
);
});

it('should warn when mispelling componentWillReceiveContext', function() {
React.createClass({
componentWillRecieveContext: function() {
return false;
},
render: function() {
return <div />;
},
});
expect(console.error.argsForCall.length).toBe(1);
expect(console.error.argsForCall[0][0]).toBe(
'Warning: A component has a method called componentWillRecieveContext(). Did you ' +
'mean componentWillReceiveContext()?'
);
});

it('should throw if a reserved property is in statics', function() {
expect(function() {
React.createClass({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,44 @@ describe 'ReactCoffeeScriptClass', ->
ReactDOM.unmountComponentAtNode container
expect(lifeCycles).toEqual ['will-unmount']

it 'will call context life cycle methods', ->
lifeCycles = []

class Child extends React.Component
@contextTypes:
foo: React.PropTypes.string

componentWillReceiveProps: (nextProps, nextContext) ->
lifeCycles.push 'receive-props', nextProps, nextContext

componentWillReceiveContext: (nextContext) ->
lifeCycles.push 'receive-context', nextContext

render: ->
span
className: @props.value

class Parent extends React.Component
@childContextTypes:
foo: React.PropTypes.string

getChildContext: ->
{ foo: 'foo' }

render: ->
React.createElement('div', {}, React.createElement(Child, { value: @props.value }))

test React.createElement(Parent, value: 'bar'), 'DIV', ''
expect(lifeCycles).toEqual []

test React.createElement(Parent, value: 'baz'), 'DIV', ''
expect(lifeCycles).toEqual [
'receive-props', { value: 'baz' }, { foo: 'foo' },
'receive-context', { foo: 'foo' }
]

ReactDOM.unmountComponentAtNode container

it 'warns when classic properties are defined on the instance,
but does not invoke them.', ->
spyOn console, 'error'
Expand Down Expand Up @@ -341,6 +379,23 @@ describe 'ReactCoffeeScriptClass', ->
Did you mean componentWillReceiveProps()?'
)

it 'should warn when misspelling componentWillReceiveContext', ->
spyOn console, 'error'
class NamedComponent extends React.Component
componentWillRecieveContext: ->
false

render: ->
span
className: 'foo'

test React.createElement(NamedComponent), 'SPAN', 'foo'
expect(console.error.calls.length).toBe 1
expect(console.error.argsForCall[0][0]).toBe(
'Warning: NamedComponent has a method called componentWillRecieveContext().
Did you mean componentWillReceiveContext()?'
)

it 'should throw AND warn when trying to access classic APIs', ->
spyOn console, 'error'
instance =
Expand Down
76 changes: 73 additions & 3 deletions src/isomorphic/modern/class/__tests__/ReactES6Class-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,11 @@ describe('ReactES6Class', function() {
componentDidMount() {
lifeCycles.push('did-mount');
}
componentWillReceiveProps(nextProps) {
lifeCycles.push('receive-props', nextProps);
componentWillReceiveProps(nextProps, nextContext) {
lifeCycles.push('receive-props', nextProps, nextContext);
}
componentWillReceiveContext(nextContext) {
lifeCycles.push('receive-context', nextContext);
}
shouldComponentUpdate(nextProps, nextState) {
lifeCycles.push('should-update', nextProps, nextState);
Expand All @@ -304,7 +307,7 @@ describe('ReactES6Class', function() {
lifeCycles = []; // reset
test(<Foo value="bar" />, 'SPAN', 'bar');
expect(lifeCycles).toEqual([
'receive-props', freeze({value: 'bar'}),
'receive-props', freeze({value: 'bar'}), {},
'should-update', freeze({value: 'bar'}), {},
'will-update', freeze({value: 'bar'}), {},
'did-update', freeze({value: 'foo'}), {},
Expand All @@ -316,6 +319,52 @@ describe('ReactES6Class', function() {
]);
});

it('will call context life cycle methods', function() {
var lifeCycles = [];

class Child extends React.Component {
componentWillReceiveProps(nextProps, nextContext) {
lifeCycles.push('receive-props', nextProps, nextContext);
}
componentWillReceiveContext(nextContext) {
lifeCycles.push('receive-context', nextContext);
}
render() {
return <span className={this.props.value} />;
}
}
Child.contextTypes = {
foo: React.PropTypes.string,
};

class Parent extends React.Component {
getChildContext() {
return {
foo: 'foo',
};
}
render() {
return (
<div><Child value={this.props.value} /></div>
);
}
}
Parent.childContextTypes = {
foo: React.PropTypes.string,
};

test(<Parent value="bar" />, 'DIV', '');
expect(lifeCycles).toEqual([]);

test(<Parent value="baz" />, 'DIV', '');
expect(lifeCycles).toEqual([
'receive-props', freeze({value: 'baz'}), freeze({foo: 'foo'}),
'receive-context', freeze({foo: 'foo'}),
]);

ReactDOM.unmountComponentAtNode(container);
});

it('warns when classic properties are defined on the instance, but does not invoke them.', function() {
spyOn(console, 'error');
var getDefaultPropsWasCalled = false;
Expand Down Expand Up @@ -399,6 +448,27 @@ describe('ReactES6Class', function() {
);
});

it('should warn when misspelling componentWillReceiveContext', function() {
spyOn(console, 'error');

class NamedComponent extends React.Component {
componentWillRecieveContext() {
return false;
}
render() {
return <span className="foo" />;
}
}
test(<NamedComponent />, 'SPAN', 'foo');

expect(console.error.calls.length).toBe(1);
expect(console.error.argsForCall[0][0]).toBe(
'Warning: ' +
'NamedComponent has a method called componentWillRecieveContext(). Did ' +
'you mean componentWillReceiveContext()?'
);
});

it('should throw AND warn when trying to access classic APIs', function() {
spyOn(console, 'error');
var instance = test(<Inner name="foo" />, 'DIV', 'foo');
Expand Down
65 changes: 65 additions & 0 deletions src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,34 @@ class NormalLifeCycles extends React.Component {
}
}

// will call context life cycle methods
class ContextChildLifeCycles extends React.Component {
props : any;
state = {};
static contextTypes = { foo: React.PropTypes.string };
componentWillReceiveProps(nextProps, nextContext) {
lifeCycles.push('receive-props', nextProps, nextContext);
}
componentWillReceiveContext(nextContext) {
lifeCycles.push('receive-context', nextContext);
}
render() {
return React.createElement('span', {className: this.props.value});
}
}

class ContextParentLifeCycles extends React.Component {
static childContextTypes = { foo: React.PropTypes.string };
getChildContext() {
return {
foo: 'foo'
};
}
render() {
return React.createElement('div', {}, React.createElement(ContextChildLifeCycles, { value: this.props.value }));
}
}

// warns when classic properties are defined on the instance,
// but does not invoke them.
var getInitialStateWasCalled = false;
Expand Down Expand Up @@ -273,6 +301,16 @@ class MisspelledComponent2 extends React.Component {
}
}

// it should warn when misspelling componentWillReceiveContext
class MisspelledComponent3 extends React.Component {
componentWillRecieveContext() {
return false;
}
render() {
return React.createElement('span', {className: 'foo'});
}
}

// it supports this.context passed via getChildContext
class ReadContext extends React.Component {
static contextTypes = { bar: React.PropTypes.string };
Expand Down Expand Up @@ -428,6 +466,20 @@ describe('ReactTypeScriptClass', function() {
]);
});

it('will call context life cycle methods', function() {
lifeCycles = [];
test(React.createElement(ContextParentLifeCycles, {value: 'bar'}), 'DIV', '');
expect(lifeCycles).toEqual([]);

test(React.createElement(ContextParentLifeCycles, {value: 'baz'}), 'DIV', '');
expect(lifeCycles).toEqual([
'receive-props', {value: 'baz'}, {foo: 'foo'},
'receive-context', {foo: 'foo'}
]);

ReactDOM.unmountComponentAtNode(container);
});

it('warns when classic properties are defined on the instance, ' +
'but does not invoke them.', function() {
spyOn(console, 'error');
Expand Down Expand Up @@ -480,6 +532,19 @@ describe('ReactTypeScriptClass', function() {
);
});

it('should warn when misspelling componentWillReceiveContext', function() {
spyOn(console, 'error');

test(React.createElement(MisspelledComponent3), 'SPAN', 'foo');

expect((<any>console.error).argsForCall.length).toBe(1);
expect((<any>console.error).argsForCall[0][0]).toBe(
'Warning: ' +
'MisspelledComponent3 has a method called componentWillRecieveContext(). ' +
'Did you mean componentWillReceiveContext()?'
);
});

it('should throw AND warn when trying to access classic APIs', function() {
spyOn(console, 'error');
var instance = test(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ describe('ReactServerRendering', function() {
componentWillReceiveProps: function() {
lifecycle.push('componentWillReceiveProps');
},
componentWillReceiveContext: function() {
lifecycle.push('componentWillReceiveContext');
},
componentWillUnmount: function() {
lifecycle.push('componentWillUnmount');
},
Expand Down Expand Up @@ -331,6 +334,9 @@ describe('ReactServerRendering', function() {
componentWillReceiveProps: function() {
lifecycle.push('componentWillReceiveProps');
},
componentWillReceiveContext: function() {
lifecycle.push('componentWillReceiveContext');
},
componentWillUnmount: function() {
lifecycle.push('componentWillUnmount');
},
Expand Down
Loading