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

Polyfill for getSnapshotBeforeUpdate #1

Merged
merged 9 commits into from
Mar 28, 2018
Merged
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
63 changes: 18 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

## What is this project?

React version 17 will deprecate several of the class component API lifecycles: `componentWillMount`, `componentWillReceiveProps`, and `componentWillUpdate`. (See [React RFC 6](https://github.com/reactjs/rfcs/pull/6) for more information about this decision.)
React version 17 will deprecate several of the class component API lifecycles: `componentWillMount`, `componentWillReceiveProps`, and `componentWillUpdate`. (Read the [Update on Async rendering blog post](https://deploy-preview-596--reactjs.netlify.com/blog/2018/03/15/update-on-async-rendering.html) to learn more about why.) A couple of new lifecycles are also being added to better support [async rendering mode](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html).

This would typically require any third party libraries dependent on those lifecycles to release a new major version in order to adhere to semver. However, the `react-lifecycles-compat` polyfill offers a way to remain compatible with older versions of React (0.14.9+). 🎉😎
Typically, this type of change would require third party libraries to release a new major version in order to adhere to semver. However, the `react-lifecycles-compat` polyfill offers a way to use the new lifecycles with older versions of React as well (0.14.9+) so no breaking release is required. This enables shared libraries to support both older and newer versions of React simultaneously.

## How can I use the polyfill

Expand All @@ -17,58 +17,31 @@ yarn add react-lifecycles-compat
npm install react-lifecycles-compat --save
```

Next, update your component to use the new static lifecycle, `getDerivedStateFromProps`. For example:
```js
// Before
class ExampleComponent extends React.Component {
state = {
derivedData: computeDerivedState(this.props)
};

componentWillReceiveProps(nextProps) {
if (this.props.someValue !== nextProps.someValue) {
this.setState({
derivedData: computeDerivedState(nextProps)
});
}
}
}

// After
class ExampleComponent extends React.Component {
// Initialize state in constructor,
// Or with a property initializer.
state = {};

static getDerivedStateFromProps(nextProps, prevState) {
if (prevState.someMirroredValue !== nextProps.someValue) {
return {
derivedData: computeDerivedState(nextProps),
someMirroredValue: nextProps.someValue
};
}

// Return null to indicate no change to state.
return null;
}
}
```
Next, update your component and replace any of the deprecated lifecycles with new ones introduced with React 16.3. (Refer to the React docs for [examples of how to use the new lifecycles](https://deploy-preview-596--reactjs.netlify.com/blog/2018/03/15/update-on-async-rendering.html).)

Lastly, use the polyfill to make your component backwards compatible with older versions of React:
Lastly, use the polyfill to make the new lifecycles work with older versions of React:
```js
import React from 'react';
import polyfill from 'react-lifecycles-compat';

class ExampleComponent extends React.Component {
static getDerivedStateFromProps(nextProps, prevState) {
// ...
}

// ...
}

// Polyfill your component to work with older versions of React:
// Polyfill your component so the new lifecycles will work with older versions of React:
polyfill(ExampleComponent);

export default ExampleComponent;
```
```

## Which lifecycles are supported?

Currently, this polyfill supports [static `getDerivedStateFromProps`](https://deploy-preview-587--reactjs.netlify.com/docs/react-component.html#static-getderivedstatefromprops) and [`getSnapshotBeforeUpdate`](https://deploy-preview-587--reactjs.netlify.com/docs/react-component.html#getsnapshotbeforeupdate)- both introduced in version 16.3.

## Validation

Note that in order for the polyfill to work, none of the following lifecycles can be defined by your component: `componentWillMount`, `componentWillReceiveProps`, or `componentWillUpdate`.

Note also that if your component contains `getSnapshotBeforeUpdate`, `componentDidUpdate` must be defined as well.

An error will be thrown if any of the above conditions are not met.
52 changes: 50 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,27 @@ function componentWillReceiveProps(nextProps) {
}
}

function componentWillUpdate(nextProps, nextState) {
try {
var prevProps = this.props;
var prevState = this.state;
this.props = nextProps;
this.state = nextState;

Choose a reason for hiding this comment

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

Won't direct assignments to these warn?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No. React sets/updates this values in a similar way before calling lifecycles.

this.__reactInternalSnapshot = this.getSnapshotBeforeUpdate(
Copy link

@Jessidhia Jessidhia Mar 28, 2018

Choose a reason for hiding this comment

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

This is the kind of thing for which a WeakMap would be good for... but can't use WeakMap in React :(

Have you tried at least making it non-enumerable, or even maybe also a polyfill-internal Symbol instead of a string name, or are those also out when supporting 0.14?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think that's necessary. React already stores other "__reactInternal" attributes on class components. And relying on a symbol might cause problems for older browsers (supported by older versions of React) that don't support symbols.

Choose a reason for hiding this comment

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

Would be nice if a future version of React would use a WeakMap for their per-instance internal state instead (React 17?) 🤔

prevProps,
prevState
);
} finally {
this.props = prevProps;
this.state = prevState;
}
}

// React may warn about cWM/cWRP/cWU methods being deprecated.
// Add a flag to suppress these warnings for this special case.
componentWillMount.__suppressDeprecationWarning = true;
componentWillReceiveProps.__suppressDeprecationWarning = true;
componentWillUpdate.__suppressDeprecationWarning = true;

module.exports = function polyfill(Component) {
if (!Component.prototype || !Component.prototype.isReactComponent) {
Expand All @@ -38,18 +55,49 @@ module.exports = function polyfill(Component) {

if (typeof Component.getDerivedStateFromProps === 'function') {
if (typeof Component.prototype.componentWillMount === 'function') {
throw new Error('Cannot polyfill if componentWillMount already exists');
throw new Error(
'Cannot polyfill getDerivedStateFromProps() for components that define componentWillMount()'
);
} else if (
typeof Component.prototype.componentWillReceiveProps === 'function'
) {
throw new Error(
'Cannot polyfill if componentWillReceiveProps already exists'
'Cannot polyfill getDerivedStateFromProps() for components that define componentWillReceiveProps()'
);
}

Component.prototype.componentWillMount = componentWillMount;
Component.prototype.componentWillReceiveProps = componentWillReceiveProps;
}

if (typeof Component.prototype.getSnapshotBeforeUpdate === 'function') {
if (typeof Component.prototype.componentWillUpdate === 'function') {
throw new Error(
'Cannot polyfill getSnapshotBeforeUpdate() for components that define componentWillUpdate()'
);
}
if (typeof Component.prototype.componentDidUpdate !== 'function') {
throw new Error(
'Cannot polyfill getSnapshotBeforeUpdate() for components that do not define componentDidUpdate() on the prototype'
);
}

Component.prototype.componentWillUpdate = componentWillUpdate;

var componentDidUpdate = Component.prototype.componentDidUpdate;

Component.prototype.componentDidUpdate = function componentDidUpdatePolyfill(
prevProps,
prevState
) {
componentDidUpdate.call(
this,
prevProps,
prevState,
this.__reactInternalSnapshot
);
};
}

return Component;
};
Loading