-
Notifications
You must be signed in to change notification settings - Fork 7.6k
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
An update on async rendering #596
Changes from 14 commits
544c0bc
e298b47
9ff0f12
0674c34
cdb4b9e
289a2da
99fedea
49464f7
fe6b133
1314117
f005c04
06d5be4
3696388
ac23b1f
5cae7c6
f70c0dd
c1e67be
75a43aa
fb3b91f
60d65ce
f632f22
626ac42
7456327
2909738
5400338
813be17
8de7dc4
c45fb40
858c1a7
98d5a09
442591c
b1ce572
2312173
16eb646
4d16523
9905159
1ca6cfc
7408e07
3c75def
55650fc
97a109d
21fa116
92cf72d
fa34fcf
b3bf0bd
254fc8b
b0c22f7
558d576
7425aed
65b1496
6eae811
a2139de
030980e
e110ac5
ce060eb
e143823
65eca09
a3ea63a
7ced9ce
7cf5b58
9f72403
712f4de
4610392
b824bd2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
--- | ||
title: Strict mode | ||
author: [bvaughn] | ||
--- | ||
|
||
`StrictMode` is a tool for highlighting potential problems in an application. Like `Fragment`, `StrictMode` does not render any visible UI. It simply activates additional checks and warnings for its descendants. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a paragraph reminding people how deprecations work and explaining how strict mode fits in?
I guess the double lifecycle thing is not planned to be global though? Or are we doing that in 17 too? I missed that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Double lifecycle is specific to strict mode. I don't think we have any plans to turn this on outside fo strict mode. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Put another way, strict mode exists for more than just advanced notification of deprecations. The double lifecycle calls are one thing. Warning about UNSAFE_ (but still supported) methods are another. Actually, the only thing about strict mode (currently) that's specific to deprecation is string refs. |
||
> Note: | ||
> | ||
> Strict mode checks are run in development mode only; _they do not impact the production build_. | ||
|
||
You can enable strict mode for any part of your application. For example: | ||
`embed:update-on-async-rendering/enabling-strict-mode.js` | ||
|
||
In the above example, strict mode checks will *not* be run against the `Header` and `Footer` components. However, `RouteOne` and `RouteTwo`, as well as all of their descendants, will have the checks. | ||
|
||
In version 16.3, `StrictMode` helps with: | ||
* [Identifying components with unsafe lifecycles](#identifying-unsafe-lifecycles) | ||
* [Warning about legacy string ref API usage](#warning-about-legacy-string-ref-api-usage) | ||
* [Detecting unexpected side effects](#detecting-unexpected-side-effects) | ||
|
||
Additional functionality will be added with future releases of React. | ||
|
||
### Identifying unsafe lifecycles | ||
|
||
As previously mentioned, certain legacy lifecycle methods are unsafe for use in async React applications. However, if your application uses third party libraries, it can be difficult to ensure that these lifecycles aren't being used. Fortunately, strict mode can help with this! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is previous mentioned as a link, but when I read this, I thought I missed some explanation paragraph. Maybe "As explained in [this blog post], ..."? Then it's clear where exactly I need to go if I land on this page first. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh good call. This was originally part of the "update on async" page. Thanks |
||
|
||
When strict mode is enabled, React compiles a list of all class components using the unsafe lifecycles, and logs a warning message with information about these components, like so: | ||
|
||
![](../images/blog/strict-mode-unsafe-lifecycles-warning.png) | ||
|
||
Addressing the issues identified by strict mode _now_ will make it easier for you to take advantage of async rendering in future releases of React. | ||
|
||
### Warning about legacy string ref API usage | ||
|
||
Previously, React provided two ways for managing refs: the legacy string ref API and the callback API. Although the string ref API was the more convenient of the two, it had [several downsides](https://github.com/facebook/react/issues/1373) and so our official recomendation was to [use the callback form instead](https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs). | ||
|
||
Version 16.3 adds a new option for managing refs that offers the convenience of a string ref without any of the downsides: | ||
`embed:16-3-release-blog-create-ref.js` | ||
|
||
> **Note:** | ||
> | ||
> Callback refs will continue to be supported in addition to the new `createRef` API. | ||
> | ||
> You don't need to replace callback refs in your components. They are slightly more flexible, so they will remain as an advanced feature. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would add below the note: "Since object refs were largely added as a replacement for string refs, strict mode now warns about usage of string refs." Otherwise it's not 100% clear how the addition of a feature relates to StrictMode |
||
[Learn more about the new `createRef` API here.](#) | ||
|
||
### Detecting unexpected side effects | ||
|
||
As a general rule, side-effects should be avoided in certain class component methods (e.g. the `constructor`, `render`, etc). This is because React may invoke these methods more than once before committing, or it may invoke them without committing at all (because of an error or a higher priority interruption). Ignoring this rule can lead to a variety of problems, including memory leaks and invalid state. Unfortunately, it can be difficult to detect these problems as they are often non-deterministic. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "committing" and "interruption" are our jargon, we never explained them. Can we avoid these terms? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know how to avoid the term "interruption". It's pretty key to the purpose of this update. I will try to think of a better way to explain commit before using it. |
||
|
||
Strict mode can't automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following methods: | ||
|
||
* Class component `constructor` method | ||
* The `render` method | ||
* `setState` updater functions | ||
* The static `getDerivedStateFromProps` lifecycle | ||
|
||
> Note: | ||
> | ||
> This only applies to development mode. _Lifecycles will not be double-invoked in production mode._ | ||
|
||
For example, consider the following code: | ||
`embed:update-on-async-rendering/side-effects-in-constructor.js` | ||
|
||
At first glance, this code might not seem problematic. But if `SharedApplicationState.recordEvent` is not idempotent, then instantiating this component multiple times could lead to invalid application state. This sort of subtle bug might not manifest during development, or it might do so inconsistently and so be overlooked. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "idempotent" should be a link to wikipedia or some explanation. We shouldn't assume React users know these terms There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm. I'm willing to add a link, but I think it's not unreasonable to assume that someone can do a Wikipedia search for a fairly common programming term if they don't know the meaning. (I have to do this all the time, myself.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've done UI programming for years and never encountered this term (until recently). I agree people can look it up, but in practice people who don't understand it will likely either skip it or feel inadequate There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've already added a link (locally). I was just expressing my point of view: I have to look up terms that I'm uncertain about regularly. I just didn't think it was necessary to have a link in this case. Each [external] link has a maintenance cost, even though Wikipedia is probably a pretty stable target. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think maybe I based the tone/tech-level of this blog post too heavily on recent ones (like the 16.0 announcement) which contain wording like so:
In comparison, it just didn't occur to me that we tried to add links for less common terms like "idempotent". 😄 |
||
|
||
By intentionally double-invoking methods like the component constructor, strict mode makes patterns like this easier to spot. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
--- | ||
title: Update on Async Rendering | ||
author: [bvaughn, gaearon] | ||
--- | ||
|
||
For the past few months, the React team has been experimenting with [asynchronous rendering](/blog/2017/09/26/react-v16.0.html#new-core-architecture), and we are very excited about the new features it enables. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a sentence akin to your later "Asynchronous rendering solves this by splitting rendering into small chunks that can be paused and later resumed." up here? Not everyone will click through but most people will appreciate a quick refresher. Or you could put it in the "We found that asynchronous rendering can help in several ways." paragraph before the list. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I quite follow what you're asking with this comment. |
||
|
||
Along the way our research has shown that some of our legacy component lifecycles tend to encourage unsafe coding practices. They are: | ||
|
||
* `componentWillMount` | ||
* `componentWillReceiveProps` | ||
* `componentWillUpdate` | ||
|
||
Because of this, we are adding an "UNSAFE_" prefix to these lifecycles in a future release. React [follows semantic versioning](/blog/2016/02/19/new-versioning-scheme.html), so the migration path is gradual: | ||
|
||
* **16.3**: Introduce aliases for the unsafe lifecycles, `UNSAFE_componentWillMount`, `UNSAFE_componentWillReceiveProps`, and `UNSAFE_componentWillUpdate`. (Both the old lifecycle names and the new aliases will work in this release.) | ||
* **16.4**: Enable deprecation warning for `componentWillMount`, `componentWillReceiveProps`, and `componentWillUpdate`. (Both the old lifecycle names and the new aliases will work in this release.) | ||
* **17.0**: Remove `componentWillMount`, `componentWillReceiveProps`, and `componentWillUpdate` . (Only the new "UNSAFE_" lifecycle names will work in this release.) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any vague sense of timing we could give here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm reluctant to make any predictions on timing. I could say "within a week or two", "within a month or two", etc. if that would be helpful? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Eh, I guess not. I think the most valuable would be to say 17 is at least X months away but honestly I have no idea |
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any plans to have a codemod to auto-prefix with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, there is The 16.3 release is intended to give library authors a head start on fixing async things in advance of the deprecation warnings being turned on. Unfortunately, just renaming the methods won't actually fix any of the potential async problems, so I don't think mentioning the codemod is a good idea for this release. |
||
In this post, we will explore some of the potential capabilities of async rendering, and we'll outline a migration plan for components that rely on these legacy lifecycles. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe "on the deprecated lifecycles" -> "on these legacy lifecycles". Since they're not technically deprecated yet. Also too many people read "deprecated" as "immediately removed" so I prefer other words when possible. |
||
## What can asynchronous rendering do? | ||
|
||
#### With every new version, our goal is to improve the user experience of apps created with React. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you want to couch this more as "make it easier for developers using React to build great user experiences"? I'm not sure which feels more empowering to React devs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I dig it. |
||
|
||
We have been fine-tuning the performance of React with every new release. However, despite what synthetic benchmarks say, we've found that the real bottleneck is generally not React itself, but the application code using it. In order to unlock the next wave of performance optimizations and new features, we need React to be smarter about when to re-render components and flush updates to the screen. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe: insert a paragraph about how React batches/coalesces updates already? I've always found that people are receptive to batching and in some sense, async rendering is an extension of that. |
||
We found that asynchronous rendering can help in several ways. For example: | ||
|
||
1. As users navigate within an app, newly displayed components often have asynchronous dependencies (including data, images, and code splitting). This leads to a lot of boilerplate code managing data fetching and displaying the loading states. It can also lead to a "cascade of spinners" as the data loads, causing DOM reflows and janky user experience. We'd like to make it easier for product developers to express asynchronous dependencies of components. React could keep the old UI "alive" and interactive for a certain period while the updated UI is not ready yet, and provide a declarative way to show a loading indicator if it takes more than a second. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is OK as-is, but I realized it may be more relatable to work in the words "wait to show a component until it has loaded all its data" – close to what you had but a little different. I think (a) it is easier to understand the initial render case than thinking about how updates need to work and (b) it is easier to relate my proposed wording to SSR and async SSR with data dependencies is one of our top feature requests so it will be familiar to many readers. |
||
2. Fast updates within a short timeframe often cause jank because React processes each update individually. We'd like to automatically "combine" updates within a few hundred milliseconds when possible so that there is less re-rendering. | ||
3. Some updates are inherently less important than others. For example, if you're writing a live-updating search filter input like [this](https://zeit.co/blog/domains-search-web#asynchronous-rendering), it is essential that the input is updated immediately (within a few milliseconds). Re-rendering the result list can be done later, and should not block the thread or cause stutter when typing. It would be nice if React had a way to mark the latter updates as having a lower priority. (Note that even debouncing the input doesn't help because if the rendering is synchronous—like in React today—a keystroke can't interrupt the rendering if it already started. Asynchronous rendering solves this by splitting rendering into small chunks that can be paused and later restarted.) | ||
4. For UI elements like hidden popups and tabs, we'd like to be able to start pre-rendering their content when the browser isn't busy. This way, they can appear instantaneously in response to a later user interaction. However, we don't want to make the initial rendering slower, so it's essential to render such elements lazily ([when the browser is idle](https://developers.google.com/web/updates/2015/08/using-requestidlecallback)). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't "lazily" mean "when needed" which is the opposite here? "However, we'd like to do this only [when the browser is idle] to avoid slowing down other parts of the page." |
||
5. For many apps, React is not the only JavaScript on the page. It often has to coordinate with other JS libraries, server-rendered widgets, and so on. Asynchronous rendering lets React better coordinate with non-React code regarding when components are inserted into the DOM so that [the user experience is smooth](https://twitter.com/acdlite/status/909926793536094209). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this truly a different facet? It is a little unclear to me what exactly this means. Andrew's demo shows that with createBatch we can get back to what the sync behavior previously was without blocking, but I wouldn't say that async rendering helps these integrations. More than these integrations are something we need to support while doing items 1-4. I think it is fine to leave this bullet out unless I'm missing something. |
||
|
||
Of course, it's possible to implement some of these features today, but it's difficult. We hope to make them effortless by building them into React itself. By replacing problematic lifecycles with safer alternatives, we also hope to make it simple to write async-safe React components. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe:
|
||
|
||
In the next section, we'll look at how to update your existing components to prepare for the upcoming lifecycle changes. | ||
|
||
## Updating class components | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is a standalone post (my preference), we will need a section hereabouts summarizing the changes. I would like us to call out that the intention of the change is to make the lifecycle methods more functional and pure. And also say why instance variables are problematic (it's sorta implicit in your post but I'd love to have a sentence people can point to for this saying it's bad). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We kind of summarized most of the relevant changes in the intro to this (standalone) post. I think the only thing we haven't explicitly mentioned yet is the new static lifecycle. I'll try to introduce this. I'm not sure on wording yet. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a section, "Before we begin, here’s a quick reminder of the lifecyle changes in version 16.3". Hopefully this is something like what yo had in mind. |
||
|
||
#### If you're an application developer, **you don't have to do anything about the deprecated methods yet**. The primary purpose of this update (v16.3) is to enable open source project maintainers to update their libraries in advance of any deprecation warnings. Those warnings will be enabled with the next minor release, v16.4. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do you feel about calling them "legacy" instead of "deprecated" throughout the post? Even the fact that they survive in the UNSAFE_ form means they're different from our usual deprecations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there's only two places I use the word "deprecated" in this post:
I don't mind replacing the second occurrence with "legacy" though. The phrase "deprecation warning" also appears twice, but I don't think "legacy warning" makes as much sense. |
||
|
||
However, if you'd like to start using the new component API (or if you're a maintainer looking to update your library in advance) here are a few examples that we hope will help you to start thinking about components a bit differently. Over time, we plan to add additional "recipes" to our documentation that show how to perform common tasks in a way that's async-safe. | ||
|
||
### Initializing state | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think people will read this as examples of things that are problematic with async mode? I would have a concern that people will interpret them that way and then get confused because (for example) this example would actually be fine in async mode. Maybe frame this section not as "examples that are async-safe" but rather just "examples that avoid the problematic methods"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I can see that possible interpretation. I didn't want to write examples that felt intentionally problematic, because I didn't want people to react by thinking, "Well just don't make that obvious mistake and you're fine". The goal of this section is to show people how to replace existing patterns that depend on the old lifecycles with similar patterns that fit within the suggested lifecycles (and also happen to be safer for async and SSR). |
||
|
||
This example shows a component with `setState` calls inside of `componentWillMount`: | ||
`embed:update-on-async-rendering/initializing-state-before.js` | ||
|
||
The simplest refactor for this type of component is to move the state-updates to the constructor or to a property initializer, like so: | ||
`embed:update-on-async-rendering/initializing-state-after.js` | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about adding
|
||
### Fetching external data | ||
|
||
Here is an example of a component that uses `componentWillMount` to fetch external data:: | ||
`embed:update-on-async-rendering/fetching-external-data-before.js` | ||
|
||
The above code is problematic for both server rendering (where the external data won't be used) and the upcoming async rendering mode (where the request might be initiated multiple times, or executed unnecessarily). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. multiple times OR unnecessarily? (not sure how these are different) Did we explain why multiple times in async in this post? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My meaning:
Suggestions for alternate wording welcome.
No, I guess not. I explained it in the Strict Mode post. I will revisit this and think about moving that explanation up to the start of this article. |
||
|
||
The upgrade path for this is to move data-fetching into `componentDidMount`: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we walk this back a little to "The recommended upgrade path for most use cases"? Since fetching in cWM is a valid case sometimes and you even note that two paragraphs down. |
||
`embed:update-on-async-rendering/fetching-external-data-after.js` | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd add:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IME the number-one reason people do this is because they have |
||
> Note: | ||
> | ||
> Some advanced use-cases (e.g. libraries like Relay) may want to experiment with eagerly prefetching async data. An example of how this can be done is available [here](https://gist.github.com/bvaughn/89700e525ff423a75ffb63b1b1e30a8f). | ||
|
||
### Adding event listeners (or subscriptions) | ||
|
||
Here is an example of a component that subscribes to an external event dispatcher when mounting: | ||
`embed:update-on-async-rendering/adding-event-listeners-before.js` | ||
|
||
Unfortunately, this can cause memory leaks for server rendering (where `componentWillUnmount` will never be called) and async rendering (where rendering might be interrupted before it completes, causing `componentWillUnmount` not to be called). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this and the lifecycle method is being deprecated 😝 might be worth calling out that there's more symmetry between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I like this way of framing it. I'm a little unsure of the wording, but I'll take a stab at it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice hammering of the point that most of these patterns are already SSR-unsafe 👍 |
||
|
||
People often assume that `componentWillMount` and `componentWillUnmount` are paired, but that is not guaranteed. Only once `componentDidMount` has been called does React guarantee that `componentWillUnmount` will later be called for clean up. | ||
|
||
For this reason, the recommended way to add listeners/subscriptions is to use the `componentDidMount` lifecycle: | ||
`embed:update-on-async-rendering/adding-event-listeners-after.js` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After this example, can we acknowledge that this is a little clunky (since it is definitely more code even without the proper cWRP here) and we'll try to improve it later? "Although this is slightly more code, this pattern means that the subscription creation can be deferred until after the component renders on screen, reducing the amount of time in the critical render path. In the future, React may include more tools to manage data fetching efficiently and reduce code complexity." something like that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great. I had the same concern. |
||
|
||
### Updating `state` based on `props` | ||
|
||
Here is an example of a component that uses the legacy `componentWillReceiveProps` lifecycle to update `state` based on new `props` values: | ||
`embed:update-on-async-rendering/updating-state-from-props-before.js` | ||
|
||
Although the above code is not problematic in itself, the `componentWillReceiveProps` lifecycle is often mis-used in ways that _do_ present problems. Because of this, the method has been deprecated. | ||
|
||
As of version 16.3, the recommended way to update `state` in response to `props` changes is using the new `static getDerivedStateFromProps` lifecycle: | ||
`embed:update-on-async-rendering/updating-state-from-props-after.js` | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add one sentence reminding folks when getDerivedStateFromProps is called here? (In particular, emphasize whether it happens on initial mount and maybe say it's pure.) |
||
> Note: | ||
> | ||
> The [`react-lifecycles-compat`](https://github.com/reactjs/react-lifecycles-compat) polyfill allows this new lifecycle to be used with older versions of React as well. This can be helpful if you're writing a shared component that is intended for use with multiple versions of React. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you intend to have both this Note and the last section independently? Maybe just merge into the last section and have this point there? Either way, can we please add a brief note suggesting app developers to not do this? That they should update to 16.3 instead. Hopefully obvious but probably worth saying anyway. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but I'll shorten this one and link below for more info. I think it's worth having some small note here in case people wonder, "won't this break compatibility with older versions of React?" |
||
|
||
### Invoking external callbacks | ||
|
||
Here is an example of a component that calls an external function when its internal state changes: | ||
`embed:update-on-async-rendering/invoking-external-callbacks-before.js` | ||
|
||
Sometimes people use `componentWillUpdate` out of a misplaced fear that by the time `componentDidUpdate` fires, it is "too late" to update the state of other components. This is not the case. React ensures that any `setState` calls that happen during `componentDidMount` and `componentDidUpdate` are flushed before the user sees the updated UI. In general, it is better to avoid cascading updates like this, but in some cases they are unavoidable (for example, if you need to position a tooltip after measuring the rendered DOM element). | ||
|
||
Either way, it is unsafe to use `componentWillUpdate` for this purpose in async mode, because the external callback might get called multiple times for a single update. Instead, the `componentDidUpdate` lifecycle should be used since it is guaranteed to be invoked only once per update: | ||
`embed:update-on-async-rendering/invoking-external-callbacks-after.js` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is calling props.onChange during render even a supported pattern? I guess it can be but it feels dicey and strikes me as not-well-thought-out data flow. (esp. if it leads to another rerender) Maybe we can replace this with something that is clearly not side effectful within the application. Performance logging came to mind but I guess that is the one case where you would want componentWillUpdate. Would really love to find a better example here before publishing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found places within Facebook code where we were doing this. That made me want to specifically call it out as a pattern to avoid. |
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd add:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like this clarification. Side note: I wonder how async will impact this. I think it will be important to at least have the option of doing a sync There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the idea is that even in async, |
||
## Other scenarios | ||
|
||
While we tried to cover the most common use cases in this post, we recognize that we might have missed some of them. If you are using `componentWillMount`, `componentWillUpdate`, or `componentWillReceiveProps` in ways that aren't covered by this blog post, and aren't sure how to migrate off these legacy lifecycles, please [file a new issue against our documentation](https://github.com/reactjs/reactjs.org/issues/new) with your code examples and as much background information as you can provide. We will update this document with new alternative patterns as they come up. | ||
|
||
## Open source project maintainers | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd add a section before this one. Something like
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Love it. |
||
|
||
Open source maintainers might be wondering what these changes mean for shared components. If you implement the above suggestions, what happens with components that depend on the new static `getDerivedStateFromProps` lifecycle? Do you also have to release a new major version and drop compatibility for React 16.2 and older? | ||
|
||
Fortunately, you do not! | ||
|
||
Along with the release of 16.3, we've also released a new NPM package, [`react-lifecycles-compat`](https://github.com/reactjs/react-lifecycles-compat). This package polyfills components so that the new `getDerivedStateFromProps` lifecycle will also work with older versions of React (0.14.9+). | ||
|
||
To use this polyfill, first add it as a dependency to your library: | ||
|
||
```bash | ||
# Yarn | ||
yarn add react-lifecycles-compat | ||
|
||
# NPM | ||
npm install react-lifecycles-compat --save | ||
``` | ||
|
||
Next, update your components to use the new static lifecycle, `getDerivedStateFromProps`, as described above. | ||
|
||
Lastly, use the polyfill to make your component backwards compatible with older versions of React: | ||
`embed:update-on-async-rendering/using-react-lifecycles-compat.js` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
class MyComponent extends React.Component { | ||
// highlight-next-line | ||
divRef = React.createRef(); | ||
|
||
render() { | ||
// highlight-next-line | ||
return <input type="text" ref={this.divRef} />; | ||
} | ||
|
||
componentDidMount() { | ||
// highlight-next-line | ||
this.divRef.value.focus(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// After | ||
class ExampleComponent extends React.Component { | ||
// highlight-range{1-3} | ||
state = { | ||
subscribedValue: this.props.dataSource.value, | ||
}; | ||
|
||
// highlight-range{1-18} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. combine this with above highlight block too? |
||
componentDidMount() { | ||
// Event listeners are only safe to add after mount, | ||
// So they won't leak if mount is interrupted or errors. | ||
this.props.dataSource.subscribe( | ||
this._onSubscriptionChange | ||
); | ||
|
||
// External values could change between render and mount, | ||
// In some cases it may be important to handle this case. | ||
if ( | ||
this.state.subscribedValue !== | ||
this.props.dataSource.value | ||
) { | ||
this.setState({ | ||
subscribedValue: this.props.dataSource.value, | ||
}); | ||
} | ||
} | ||
|
||
componentWillUnmount() { | ||
this.props.dataSource.unsubscribe( | ||
this._onSubscriptionChange | ||
); | ||
} | ||
|
||
_onSubscriptionChange = subscribedValue => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: we use |
||
this.setState({subscribedValue}); | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Before | ||
class ExampleComponent extends React.Component { | ||
// highlight-range{1-10} | ||
componentWillMount() { | ||
this.setState({ | ||
subscribedValue: this.props.dataSource.value, | ||
}); | ||
|
||
// This is not safe; it can leak! | ||
this.props.dataSource.subscribe( | ||
this._onSubscriptionChange | ||
); | ||
} | ||
|
||
componentWillUnmount() { | ||
this.props.dataSource.unsubscribe( | ||
this._onSubscriptionChange | ||
); | ||
} | ||
|
||
_onSubscriptionChange = subscribedValue => { | ||
this.setState({subscribedValue}); | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import React from 'react'; | ||
|
||
// highlight-next-line | ||
const {StrictMode} = React; | ||
|
||
function ExampleApplication() { | ||
return ( | ||
<div> | ||
<Header /> | ||
{/* highlight-next-line */} | ||
<StrictMode> | ||
<> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unnecessary frag? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to avoid the appearance that |
||
<RouteOne /> | ||
<RouteTwo /> | ||
</> | ||
{/* highlight-next-line */} | ||
</StrictMode> | ||
<Footer /> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// After | ||
class ExampleComponent extends React.Component { | ||
// highlight-next-line | ||
_hasUnmounted = false; | ||
|
||
state = { | ||
externalData: null, | ||
}; | ||
|
||
// highlight-range{1-7} | ||
componentDidMount() { | ||
asyncLoadData(this.props.someId).then(externalData => { | ||
if (!this._hasUnmounted) { | ||
this.setState({externalData}); | ||
} | ||
}); | ||
} | ||
|
||
// highlight-range{1-3} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code snippet is very stripey, can we combine the highlight blocks? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm. I guess 😄 Seems a bit subjective. |
||
componentWillUnmount() { | ||
this._hasUnmounted = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wish we could show cancellation here instead of the bool flag. Maybe we can ditch promises and just do
wdyt? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initial thought: Ooh, yes this makes for a much cleaner examples. Almost immediately followed by: But is this doing a disservice to the many people who use promises in their apps and will occasionally get the unmounted-set-state warnings? I don't know the right answer. I'll go with the cancellable form you show for now! |
||
} | ||
|
||
render() { | ||
if (this.externalData === null) { | ||
// Render loading state ... | ||
} else { | ||
// Render real UI ... | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// Before | ||
class ExampleComponent extends React.Component { | ||
state = { | ||
externalData: null, | ||
}; | ||
|
||
// highlight-range{1-5} | ||
componentWillMount() { | ||
asyncLoadData(this.props.someId).then(externalData => | ||
this.setState({externalData}) | ||
); | ||
} | ||
|
||
render() { | ||
if (this.externalData === null) { | ||
// Render loading state ... | ||
} else { | ||
// Render real UI ... | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// After | ||
class ExampleComponent extends React.Component { | ||
// highlight-range{1-4} | ||
state = { | ||
currentColor: this.props.defaultColor, | ||
palette: 'rgb', | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Before | ||
class ExampleComponent extends React.Component { | ||
state = {}; | ||
|
||
// highlight-range{1-6} | ||
componentWillMount() { | ||
this.setState({ | ||
currentColor: this.props.defaultColor, | ||
palette: 'rgb', | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// After | ||
class ExampleComponent extends React.Component { | ||
// highlight-range{1-8} | ||
componentDidUpdate(prevProps, prevState) { | ||
if ( | ||
this.state.someStatefulValue !== | ||
prevState.someStatefulValue | ||
) { | ||
this.props.onChange(this.state.someStatefulValue); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Strict Mode to be consistent with blog post title capitalization elsewhere