From eeaf72f9aa4f9b12e4d33f9a2dcd495453c81aad Mon Sep 17 00:00:00 2001 From: Jim Date: Wed, 2 Dec 2015 18:07:52 -0800 Subject: [PATCH] Added post about upgrading your code to avoid isMounted() --- _posts/2015-12-16-ismounted-antipattern.md | 77 ++++++++++++++++++++++ docs/ref-02-component-api.md | 4 +- 2 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 _posts/2015-12-16-ismounted-antipattern.md diff --git a/_posts/2015-12-16-ismounted-antipattern.md b/_posts/2015-12-16-ismounted-antipattern.md new file mode 100644 index 00000000..b258e92e --- /dev/null +++ b/_posts/2015-12-16-ismounted-antipattern.md @@ -0,0 +1,77 @@ +--- +title: "isMounted is an Antipattern" +author: jimfb +--- + +As we move closer to officially deprecating isMounted, it's worth understanding why the function is an antipattern, and how to write code without the isMounted function. + +The primary use case for `isMounted()` is to avoid calling `setState()` after a component has unmounted, because calling `setState()` after a component has unmounted will emit a warning. The “setState warning” exists to help you catch bugs, because calling `setState()` on an unmounted component is an indication that your app/component has somehow failed to clean up properly. Specifically, calling `setState()` in an unmounted component means that your app is still holding a reference to the component after the component has been unmounted - which often indicates a memory leak! + +To avoid the error message, people often add lines like this: + +```js +if(this.isMounted()) { // This is bad. + this.setState({...}); +} +``` + +Checking `isMounted` before calling `setState()` does eliminate the warning, but it also defeats the purpose of the warning, since now you will never get the warning (even when you should!) + +Other uses of `isMounted()` are similarly erroneous; using `isMounted()` is a code smell because the only reason you would check is because you think you might be holding a reference after the component has unmounted. + +An easy migration strategy for anyone upgrading their code to avoid `isMounted()` is to track the mounted status yourself. Just set a `_isMounted` property to true in `componentDidMount` and set it to false in `componentWillUnmount`, and use this variable to check your component's status. + +An optimal solution would be to find places where `setState()` might be called after a component has unmounted, and fix them. Such situations most commonly occur due to callbacks, when a component is waiting for some data and gets unmounted before the data arrives. Ideally, any callbacks should be canceled in `componentWillUnmount`, prior to unmounting. + +For instance, if you are using a Flux store in your component, you must unsubscribe in `componentWillUnmount`: + +```javascript{9} +class MyComponent extends React.Component { + componentDidMount() { + mydatastore.subscribe(this); + } + render() { + ... + } + componentWillUnmount() { + mydatastore.unsubscribe(this); + } +} +``` + +If you use ES6 promises, you may need to wrap your promise in order to make it cancelable. + +```js +const cancelablePromise = makeCancelable( + new Promise(r => component.setState({...}})) +); + +cancelablePromise + .promise + .then(() => console.log('resolved')) + .catch((reason) => console.log('isCanceled', reason.isCanceled)); + +cancelablePromise.cancel(); // Cancel the promise +``` + +Where `makeCancelable` is [defined by @istarkov](https://github.com/facebook/react/issues/5465#issuecomment-157888325) as: + +```js +const makeCancelable = (promise) => { + let hasCanceled_ = false; + + return { + promise: new Promise( + (resolve, reject) => promise + .then(r => hasCanceled_ + ? reject({isCanceled: true}) + : resolve(r) + ) + ), + cancel() { + hasCanceled_ = true; + }, + }; +}; +``` +As an added bonus for getting your code cleaned up early, getting rid of `isMounted()` makes it one step easier for you to upgrade to ES6 classes, where using `isMounted()` is already prohibited. Happy coding! diff --git a/docs/ref-02-component-api.md b/docs/ref-02-component-api.md index 68809cae..216dd2cd 100644 --- a/docs/ref-02-component-api.md +++ b/docs/ref-02-component-api.md @@ -106,7 +106,7 @@ boolean isMounted() > Note: > -> This method is not available on ES6 `class` components that extend `React.Component`. It may be removed entirely in a future version of React. +> This method is not available on ES6 `class` components that extend `React.Component`. It will likely be removed entirely in a future version of React, so you might as well [start migrating away from isMounted() now](/react/blog/2015/12/16/ismounted-antipattern.html). ### setProps @@ -143,4 +143,4 @@ Like `setProps()` but deletes any pre-existing props instead of merging the two > Note: > -> This method is deprecated and will be removed soon. This method is not available on ES6 `class` components that extend `React.Component`. Instead of calling `replaceProps`, try invoking ReactDOM.render() again with the new props. For additional notes, see our [blog post about using the Top Level API](/react/blog/2015/10/01/react-render-and-top-level-api.html) \ No newline at end of file +> This method is deprecated and will be removed soon. This method is not available on ES6 `class` components that extend `React.Component`. Instead of calling `replaceProps`, try invoking ReactDOM.render() again with the new props. For additional notes, see our [blog post about using the Top Level API](/react/blog/2015/10/01/react-render-and-top-level-api.html)