Skip to content

Update to match 16.4 gDSFP semantics #27

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
179 changes: 139 additions & 40 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,111 @@
* LICENSE file in the root directory of this source tree.
*/

function componentWillMount() {
// Call this.constructor.gDSFP to support sub-classes.
var state = this.constructor.getDerivedStateFromProps(this.props, this.state);
if (state !== null && state !== undefined) {
this.setState(state);
// Conceptually, getDerivedStateFromProps is like a setState updater function.
// But it needs to be called *after* all the other updates in the queue, and it
// must be called exactly once, right before shouldComponentUpdate.
//
// Other than getDerivedStateFormProps itself, there's no lifecycle that meets
// these requirements. componentWillReceiveProps fires only if the props have
// changed. componentWillUpdate fires too late.
//
// This polyfill works by monkeypatching instance.updater. updater is an
// internal-ish API that has stayed mostly stable across several major versions
// of React. The polyfill intercepts updates before they are added to the native
// update queue. Then it schedules a single update with the native update queue,
// in the form of an updater function. Inside that updater function, the
// intercepted updates are processed. Finally, getDerivedStateFromProps is
// called and applied. This approach guarantees that getDerivedStateFromProps is
// always called at the end.
function processUpdateQueue(prevState, nextProps) {
var queue = this.__updateQueue;
this.__updateQueue = null;
if (queue === null || queue === undefined) {
return null;
}
// First process all the user-provided updates
var nextState = prevState;
var update = null;
var payload = null;
var callback = null;
for (let i = 0; i < queue.length; i++) {
update = queue[i];
payload = update.payload;
callback = update.callback;
if (typeof payload === 'function') {
payload = payload.call(this, prevState, nextProps);
}
if (payload !== null && payload !== undefined) {
nextState = Object.assign({}, prevState, payload);
}
if (callback !== null) {
let callbacks = this.__callbacks;
if (callbacks === null || callbacks === undefined) {
this.__callbacks = [callback];
} else {
callbacks.push(callback);
}
}
}
// Now that we've processed all the updates, we can call
// `getDerivedStateFromProps`. This always comes last.
var derivedState =
this.constructor.getDerivedStateFromProps(nextProps, nextState);
if (derivedState !== null && derivedState !== undefined) {
nextState = Object.assign({}, prevState, derivedState);
}
return nextState;
}

function componentWillReceiveProps(nextProps) {
// Call this.constructor.gDSFP to support sub-classes.
// Use the setState() updater to ensure state isn't stale in certain edge cases.
function updater(prevState) {
var state = this.constructor.getDerivedStateFromProps(nextProps, prevState);
return state !== null && state !== undefined ? state : null;
}
// Binding "this" is important for shallow renderer support.
this.setState(updater.bind(this));
function componentWillMount() {
const originalUpdater = this.updater;

this.updater = {
enqueueSetState(instance, payload, callback) {
var update = {
payload: payload,
callback: callback === undefined ? null : callback
};
let queue = instance.__updateQueue;
if (queue === null || queue === undefined) {
// Create a queue of updates. This will act as a polyfill for the native
// update queue.
queue = instance.__updateQueue = [update];
// The native update queue should contain a single update function.
// In that function, we will process all the updates in the polyfilled
// queue. This allows us to call `getDerivedStateFromProps` after all
// the other updates have been processed.
originalUpdater.enqueueSetState(instance, processUpdateQueue);
} else {
// We already scheduled an update on the native queue. Push onto the
// polyfilled queue.
queue.push(update);
}
},
enqueueReplaceState() {
// Not worth implementing this since class components do no have a
// `replaceState` method.
throw new Error(
'react-lifecycles-compat: enqueueReplaceState is not supported'
);
},
// Note: Because forceUpdate does not accept an updater function, we can't
// polyfill this correctly unless we're inside batchedUpdates. So gDSFP
// will not fire unless we receive new props OR there's another update in
// the same batch.
enqueueForceUpdate: originalUpdater.enqueueForceUpdate,
enqueueCallback(instance, callback) {
instance.updater.enqueueSetState(instance, null, callback);
}
};

// Add an empty update to the queue to trigger getDerivedStateFromProps
this.setState(null);
}

function componentWillReceiveProps() {
// Add an empty update to the queue to trigger getDerivedStateFromProps.
this.setState(null);
}

function componentWillUpdate(nextProps, nextState) {
Expand Down Expand Up @@ -109,33 +197,21 @@ export function polyfill(Component) {
);
}

// React <= 16.2 does not support static getDerivedStateFromProps.
// As a workaround, use cWM and cWRP to invoke the new static lifecycle.
// Newer versions of React will ignore these lifecycles if gDSFP exists.
if (typeof Component.getDerivedStateFromProps === 'function') {
prototype.componentWillMount = componentWillMount;
prototype.componentWillReceiveProps = componentWillReceiveProps;
}

// React <= 16.2 does not support getSnapshotBeforeUpdate.
// As a workaround, use cWU to invoke the new lifecycle.
// Newer versions of React will ignore that lifecycle if gSBU exists.
if (typeof prototype.getSnapshotBeforeUpdate === 'function') {
if (typeof prototype.componentDidUpdate !== 'function') {
throw new Error(
'Cannot polyfill getSnapshotBeforeUpdate() for components that do not define componentDidUpdate() on the prototype'
);
var componentDidUpdate = prototype.componentDidUpdate;
function componentDidUpdatePolyfill(prevProps, prevState, maybeSnapshot) {
if (typeof Component.getDerivedStateFromProps === 'function') {
// If getDerivedStateFromProps is defined, we polyfilled the update queue.
// Flush the callbacks here.
var callbacks = this.__callbacks;
if (callbacks !== null && callbacks !== undefined) {
this.__callbacks = null;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i].call(this);
}
}
}

prototype.componentWillUpdate = componentWillUpdate;

var componentDidUpdate = prototype.componentDidUpdate;

prototype.componentDidUpdate = function componentDidUpdatePolyfill(
prevProps,
prevState,
maybeSnapshot
) {
if (typeof componentDidUpdate === 'function') {
// 16.3+ will not execute our will-update method;
// It will pass a snapshot value to did-update though.
// Older versions will require our polyfilled will-update value.
Expand All @@ -149,7 +225,30 @@ export function polyfill(Component) {
: maybeSnapshot;

componentDidUpdate.call(this, prevProps, prevState, snapshot);
};
}
}

// React <= 16.2 does not support static getDerivedStateFromProps.
// As a workaround, use cWM and cWRP to invoke the new static lifecycle.
// Newer versions of React will ignore these lifecycles if gDSFP exists.
if (typeof Component.getDerivedStateFromProps === 'function') {
prototype.componentWillMount = componentWillMount;
prototype.componentWillReceiveProps = componentWillReceiveProps;
prototype.componentDidUpdate = componentDidUpdatePolyfill;
}

// React <= 16.2 does not support getSnapshotBeforeUpdate.
// As a workaround, use cWU to invoke the new lifecycle.
// Newer versions of React will ignore that lifecycle if gSBU exists.
if (typeof prototype.getSnapshotBeforeUpdate === 'function') {
prototype.componentWillUpdate = componentWillUpdate;
if (typeof prototype.componentDidUpdate !== 'function') {
throw new Error(
'Cannot polyfill getSnapshotBeforeUpdate() for components that do ' +
'not define componentDidUpdate() on the prototype'
);
}
prototype.componentDidUpdate = componentDidUpdatePolyfill;
}

return Component;
Expand Down
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@
"react-lifecycles-compat.js",
"react-lifecycles-compat.min.js"
],
"babel": {
"presets": [
"env"
]
},
"devDependencies": {
"babel-jest": "^23.0.0",
"babel-preset-env": "^1.7.0",
"camelcase": "^5.0.0",
"chalk": "^2.3.0",
"eslint": "^4.16.0",
Expand Down
26 changes: 16 additions & 10 deletions react/16.3/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ fbjs@^0.8.16, fbjs@^0.8.9:
ua-parser-js "^0.7.9"

iconv-lite@~0.4.13:
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
version "0.4.23"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
dependencies:
safer-buffer ">= 2.1.2 < 3"

is-stream@^1.0.1:
version "1.1.0"
Expand Down Expand Up @@ -87,17 +89,17 @@ prop-types@^15.6.0:
object-assign "^4.1.1"

[email protected]:
version "16.3.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.0.tgz#b318e52184188ecb5c3e81117420cca40618643e"
version "16.3.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.2.tgz#cb90f107e09536d683d84ed5d4888e9640e0e4df"
dependencies:
fbjs "^0.8.16"
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.0"

react-is@^16.3.2:
version "16.3.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.2.tgz#f4d3d0e2f5fbb6ac46450641eb2e25bf05d36b22"
version "16.4.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.0.tgz#cc9fdc855ac34d2e7d9d2eb7059bbc240d35ffcf"

[email protected]:
version "16.3.2"
Expand All @@ -109,21 +111,25 @@ [email protected]:
react-is "^16.3.2"

[email protected]:
version "16.3.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.3.0.tgz#fc5a01c68f91e9b38e92cf83f7b795ebdca8ddff"
version "16.3.2"
resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9"
dependencies:
fbjs "^0.8.16"
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.0"

"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"

setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"

ua-parser-js@^0.7.9:
version "0.7.17"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
version "0.7.18"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed"

whatwg-fetch@>=0.10.0:
version "2.0.4"
Expand Down
12 changes: 12 additions & 0 deletions react/16.4/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"private": true,
"devDependencies": {
"create-react-class": "^15.6.2",
"react": "16.4",
"react-dom": "16.4",
"react-test-renderer": "16.4"
},
"scripts": {
"test": "jest test.js"
}
}
Loading