├── .github └── ISSUE_TEMPLATE │ └── config.yml ├── 0000-template.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md └── text ├── 0002-new-version-of-context.md ├── 0006-static-lifecycle-methods.md ├── 0017-new-create-ref.md ├── 0030-ref-forwarding.md ├── 0033-new-commit-phase-lifecycles.md ├── 0051-profiler.md ├── 0063-memo.md ├── 0064-lazy.md ├── 0065-contexttype.md ├── 0068-react-hooks.md ├── 0118-lazy-context-propagation.md ├── 0139-profiler-measure-commit-durations.md ├── 0147-use-mutable-source.md ├── 0188-server-components.md ├── 0212-react-18.md ├── 0213-suspense-in-react-18.md ├── 0214-use-sync-external-store.md ├── 0215-server-errors-in-react-18.md └── 0227-server-module-conventions.md /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: ⚛️ Feature Request or Proposal 3 | about: To submit a feature request or a proposal, follow the RFC process and send a pull request. Do NOT create issues for proposals. 4 | url: https://github.com/reactjs/rfcs#what-the-process-is 5 | - name: 📃 Documentation Issue 6 | url: https://github.com/reactjs/reactjs.org/issues/new 7 | about: This issue tracker is not for documentation issues. Please file documentation issues here. 8 | - name: 🤔 Questions and Help 9 | url: https://reactjs.org/community/support.html 10 | about: This issue tracker is not for support questions. Please refer to the React community's help and discussion forums. 11 | -------------------------------------------------------------------------------- /0000-template.md: -------------------------------------------------------------------------------- 1 | - Start Date: (fill me in with today's date, YYYY-MM-DD) 2 | - RFC PR: (leave this empty) 3 | - React Issue: (leave this empty) 4 | 5 | # Summary 6 | 7 | Brief explanation of the feature. 8 | 9 | # Basic example 10 | 11 | If the proposal involves a new or changed API, include a basic code example. 12 | Omit this section if it's not applicable. 13 | 14 | # Motivation 15 | 16 | Why are we doing this? What use cases does it support? What is the expected 17 | outcome? 18 | 19 | Please focus on explaining the motivation so that if this RFC is not accepted, 20 | the motivation could be used to develop alternative solutions. In other words, 21 | enumerate the constraints you are trying to solve without coupling them too 22 | closely to the solution you have in mind. 23 | 24 | # Detailed design 25 | 26 | This is the bulk of the RFC. Explain the design in enough detail for somebody 27 | familiar with React to understand, and for somebody familiar with the 28 | implementation to implement. This should get into specifics and corner-cases, 29 | and include examples of how the feature is used. Any new terminology should be 30 | defined here. 31 | 32 | # Drawbacks 33 | 34 | Why should we *not* do this? Please consider: 35 | 36 | - implementation cost, both in term of code size and complexity 37 | - whether the proposed feature can be implemented in user space 38 | - the impact on teaching people React 39 | - integration of this feature with other existing and planned features 40 | - cost of migrating existing React applications (is it a breaking change?) 41 | 42 | There are tradeoffs to choosing any path. Attempt to identify them here. 43 | 44 | # Alternatives 45 | 46 | What other designs have been considered? What is the impact of not doing this? 47 | 48 | # Adoption strategy 49 | 50 | If we implement this proposal, how will existing React developers adopt it? Is 51 | this a breaking change? Can we write a codemod? Should we coordinate with 52 | other projects or libraries? 53 | 54 | # How we teach this 55 | 56 | What names and terminology work best for these concepts and why? How is this 57 | idea best presented? As a continuation of existing React patterns? 58 | 59 | Would the acceptance of this proposal mean the React documentation must be 60 | re-organized or altered? Does it change how React is taught to new developers 61 | at any level? 62 | 63 | How should this feature be taught to existing React developers? 64 | 65 | # Unresolved questions 66 | 67 | Optional, but suggested for first drafts. What parts of the design are still 68 | TBD? -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.fb.com/codeofconduct) so that you can understand what actions will and will not be tolerated. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to rfcs 2 | We want to make contributing to this project as easy and transparent as 3 | possible. 4 | 5 | ## Pull Requests 6 | We actively welcome your pull requests. 7 | 8 | 1. Fork the repo and create your branch from `master`. 9 | 2. If you've added code that should be tested, add tests. 10 | 3. If you've changed APIs, update the documentation. 11 | 4. Ensure the test suite passes. 12 | 5. Make sure your code lints. 13 | 6. If you haven't already, complete the Contributor License Agreement ("CLA"). 14 | 15 | ## Contributor License Agreement ("CLA") 16 | In order to accept your pull request, we need you to submit a CLA. You only need 17 | to do this once to work on any of Facebook's open source projects. 18 | 19 | Complete your CLA here: 20 | 21 | ## Issues 22 | We use GitHub issues to track public bugs. Please ensure your description is 23 | clear and has sufficient instructions to be able to reproduce the issue. 24 | 25 | Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe 26 | disclosure of security bugs. In those cases, please go through the process 27 | outlined on that page and do not file a public issue. 28 | 29 | ## License 30 | By contributing to rfcs, you agree that your contributions will be licensed 31 | under the LICENSE file in the root directory of this source tree. -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013-present, Facebook, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React RFCs 2 | 3 | Many changes, including bug fixes and documentation improvements can be 4 | implemented and reviewed via the normal GitHub pull request workflow. 5 | 6 | Some changes though are "substantial", and we ask that these be put 7 | through a bit of a design process and produce a consensus among the React 8 | core team. 9 | 10 | The "RFC" (request for comments) process is intended to provide a 11 | consistent and controlled path for new features to enter the project. 12 | 13 | [Active RFC List](https://github.com/reactjs/rfcs/pulls) 14 | 15 | 16 | ## Contributor License Agreement (CLA) 17 | 18 | In order to accept your pull request, we need you to submit a CLA. You only need 19 | to do this once, so if you've done this for another Facebook open source 20 | project, you're good to go. If you are submitting a pull request for the first 21 | time, just let us know that you have completed the CLA and we can cross-check 22 | with your GitHub username. 23 | 24 | **[Complete your CLA here.](https://code.facebook.com/cla)** 25 | 26 | ## When to follow this process 27 | 28 | You should consider using this process if you intend to make "substantial" 29 | changes to React or its documentation. Some examples that would benefit 30 | from an RFC are: 31 | 32 | - A new feature that creates new API surface area, and would 33 | require a feature flag if introduced. 34 | - The removal of features that already shipped as part of the release 35 | channel. 36 | - The introduction of new idiomatic usage or conventions, even if they 37 | do not include code changes to React itself. 38 | 39 | Some changes do not require an RFC: 40 | 41 | - Rephrasing, reorganizing or refactoring 42 | - Addition or removal of warnings 43 | - Additions that strictly improve objective, numerical quality 44 | criteria (speedup, better browser support) 45 | - Additions only likely to be _noticed by_ other implementors-of-React, 46 | invisible to users-of-React. 47 | 48 | ## What to expect 49 | 50 | It is hard to write an RFC that would get accepted. Nevertheless, this shouldn't 51 | discourage you from writing one. 52 | 53 | React has a very limited API surface area, and each feature needs to work seamlessly with all other features. 54 | Even among the team members who work on React full time every day, ramping up 55 | and gaining enough context to write a good RFC takes more than a year. 56 | 57 | In practice, React RFCs serve two purposes: 58 | 59 | * **React Team RFCs** are submitted by [React Team members](https://reactjs.org/community/team.html) after extensive (sometimes, 60 | multi-month or multi-year) design, discussion, and experimentation. In practice, they comprise the 61 | majority of the RFCs that got merged so far. The purpose of these RFCs is to preview the design 62 | for the community and to provide an opportunity for feedback. We read every comment on the RFCs 63 | we publish, respond to questions, and sometimes incorporate the feedback into the proposal. 64 | Since our time is limited, we don't tend to write an RFC for a React feature unless we're very 65 | confident that it fits the design. Although it might look like most React Team RFCs easily 66 | get accepted, in practice it's because 98% of ideas were left on the cutting room floor. The remaining 67 | 2% that we feel very confident and have team consensus on about are the ones that we announce as RFCs for community feedback. 68 | 69 | * **Community RFCs** can be submitted by anyone. In practice, most community RFCs do not get merged. 70 | The most common reasons we reject an RFC is that it has significant design gaps or flaws, does not work 71 | cohesively with all the other features, or does not fall into our view of the scope of React. However, 72 | getting merged is not the only success criteria for an RFC. Even when the API design does not match 73 | the direction we'd like to take, we find RFC discussions very valuable for research and inspiration. 74 | We don't always review community RFCs in a timely manner, but whenever we start work on a related area, we check 75 | the RFCs in that area, and review the use cases and concerns that the community members have posted. 76 | When you send an RFC, your primary goal should not be necessarily to get it merged into React as is, 77 | but to generate a rich discussion with the community members. If your proposal later becomes accepted, 78 | that's great. But even if it doesn't, it won't be in vain. The resulting discussion often informs the next 79 | proposal in the same problem space, whether it comes from the community or from the React Team. Many library 80 | authors are reading the discussions, so RFCs often lead to community experimentation and userland solutions. 81 | 82 | We apply the same level of rigour both to React Team RFCs and Community RFCs. The primary difference 83 | between them is in the design phase: React Team RFCs tend to be submitted at the end of the design 84 | process whereas the Community RFCs tend to be submitted at the beginning as a way to kickstart it. 85 | 86 | ## What the process is 87 | 88 | In short, to get a major feature added to React, one usually first gets 89 | the RFC merged into the RFC repo as a markdown file. At that point the RFC 90 | is 'active' and may be implemented with the goal of eventual inclusion 91 | into React. 92 | 93 | * Fork the RFC repo http://github.com/reactjs/rfcs 94 | * Copy `0000-template.md` to `text/0000-my-feature.md` (where 95 | 'my-feature' is descriptive. Don't assign an RFC number yet). 96 | * Fill in the RFC. Put care into the details: **RFCs that do not 97 | present convincing motivation, demonstrate understanding of the 98 | impact of the design, or are disingenuous about the drawbacks or 99 | alternatives tend to be poorly-received**. 100 | * Submit a pull request. As a pull request the RFC will receive design 101 | feedback from the larger community, and the author should be prepared 102 | to revise it in response. 103 | * Build consensus and integrate feedback. RFCs that have broad support 104 | are much more likely to make progress than those that don't receive any 105 | comments. 106 | * Eventually, the team will decide whether the RFC is a candidate 107 | for inclusion in React. Note that a team review may take a long time, 108 | and we suggest that you ask members of the community to review it first. 109 | * RFCs that are candidates for inclusion in React will enter a "final comment 110 | period" lasting 3 calendar days. The beginning of this period will be signaled with a 111 | comment and tag on the RFCs pull request. 112 | * An RFC can be modified based upon feedback from the team and community. 113 | Significant modifications may trigger a new final comment period. 114 | * An RFC may be rejected by the team after public discussion has settled 115 | and comments have been made summarizing the rationale for rejection. A member of 116 | the team should then close the RFCs associated pull request. 117 | * An RFC may be accepted at the close of its final comment period. A team 118 | member will merge the RFCs associated pull request, at which point the RFC will 119 | become 'active'. 120 | 121 | 122 | ## The RFC lifecycle 123 | 124 | Once an RFC becomes active, then authors may implement it and submit the 125 | feature as a pull request to the React repo. Becoming 'active' is not a rubber 126 | stamp, and in particular still does not mean the feature will ultimately 127 | be merged; it does mean that the core team has agreed to it in principle 128 | and are amenable to merging it. 129 | 130 | Furthermore, the fact that a given RFC has been accepted and is 131 | 'active' implies nothing about what priority is assigned to its 132 | implementation, nor whether anybody is currently working on it. 133 | 134 | Modifications to active RFCs can be done in followup PRs. We strive 135 | to write each RFC in a manner that it will reflect the final design of 136 | the feature; but the nature of the process means that we cannot expect 137 | every merged RFC to actually reflect what the end result will be at 138 | the time of the next major release; therefore we try to keep each RFC 139 | document somewhat in sync with the language feature as planned, 140 | tracking such changes via followup pull requests to the document. 141 | 142 | ## Implementing an RFC 143 | 144 | The author of an RFC is not obligated to implement it. Of course, the 145 | RFC author (like any other developer) is welcome to post an 146 | implementation for review after the RFC has been accepted. 147 | 148 | If you are interested in working on the implementation for an 'active' 149 | RFC, but cannot determine if someone else is already working on it, 150 | feel free to ask (e.g. by leaving a comment on the associated issue). 151 | 152 | ## Reviewing RFCs 153 | 154 | Currently, the React Team cannot commit to reviewing RFCs in a timely manner. 155 | When you submit an RFC, your primary goal should be to solicit community feedback 156 | and generate a rich discussion. The React Team re-evaluates the current list of 157 | projects and priorities every several months. Even if an RFC is well-designed, 158 | we often can't commit to integrating it right away. However, we find it very 159 | valuable to revisit the open RFCs every few months, and see if anything catches 160 | our eye. Whenever we start working on a new problem space, we also make sure 161 | to check for prior work and discussion in any related RFCs, and engage with them. 162 | 163 | We read all RFCs within a few weeks of submission. If we think the design fits React well, 164 | and if we're ready to evaluate it, we will try to review it sooner. If we're hesitant about 165 | the design or if we don't have enough information to evaluate it, we will leave it open 166 | until it receives enough community feedback. We recognize it is frustrating to not receive 167 | a timely review, but you can be sure that none of the work you put into an RFC is in vain. 168 | 169 | ## Inspiration 170 | 171 | React's RFC process owes its inspiration to the [Yarn RFC process], [Rust RFC process], and [Ember RFC process]. 172 | 173 | [Yarn RFC process]: https://github.com/yarnpkg/rfcs 174 | [Rust RFC process]: https://github.com/rust-lang/rfcs 175 | [Ember RFC process]: https://github.com/emberjs/rfcs 176 | 177 | We've changed it in the past in response to feedback, and we're open to changing it again if needed. 178 | -------------------------------------------------------------------------------- /text/0002-new-version-of-context.md: -------------------------------------------------------------------------------- 1 | * Start Date: 2017-12-05 2 | * RFC PR: https://github.com/reactjs/rfcs/pull/2 3 | * React Issue: https://github.com/facebook/react/pull/11818 4 | 5 | # Summary 6 | 7 | Introduce a new version of context that addresses existing limitations. 8 | 9 | # Basic example 10 | 11 | ```js 12 | type Theme = 'light' | 'dark'; 13 | // Pass a default theme to ensure type correctness 14 | const ThemeContext: Context = React.createContext('light'); 15 | 16 | class ThemeToggler extends React.Component { 17 | state = {theme: 'light'}; 18 | render() { 19 | return ( 20 | // Pass the current context value to the Provider's `value` prop. 21 | // Changes are detected using strict comparison (Object.is) 22 | 23 | 31 | {this.props.children} 32 | 33 | ); 34 | } 35 | } 36 | 37 | class Title extends React.Component { 38 | render() { 39 | return ( 40 | // The Consumer uses a render prop API. Avoids conflicts in the 41 | // props namespace. 42 | 43 | {theme => ( 44 |

45 | {this.props.children} 46 |

47 | )} 48 |
49 | ); 50 | } 51 | } 52 | ``` 53 | 54 | # Motivation 55 | 56 | Typically, data in a React application is passed top-down (parent to child) via 57 | props. But sometimes it's useful to pass values through multiple levels of 58 | abstraction without involving each intermediate. Examples include a locale, or a 59 | UI theme. Many components may rely on those but you don't want to have to pass a 60 | `locale` prop and a `theme` prop through every level of the tree. 61 | 62 | Context in React provides a mechanism for a child component to access a value in 63 | an ancestor component. In this document, we'll refer to the ancestor as the 64 | **provider** and the child as the **consumer**. 65 | 66 | ## Drawbacks of the existing version of context 67 | 68 | ### shouldComponentUpdate blocks context changes 69 | 70 | The main flaw with context today is how it interacts with 71 | `shouldComponentUpdate`. If an intermediate component bails out using 72 | `shouldComponentUpdate`, and there are no pending updates further down the tree, 73 | React will assume there are no changes and reuse the entire subtree. If the 74 | subtree contains a context consumer, the consumer will not receive the latest 75 | context. In other words, context changes will not propagate through a component 76 | whose `shouldComponentUpdate` return false. 77 | 78 | `shouldComponentUpdate` is a fairly common optimization in React applications. A 79 | shared component or open source library can't assume that applications won't use 80 | it. In practice, this means that context alone is not a reliable way to 81 | broadcast changes. 82 | 83 | ### Shifts complexity to user space 84 | 85 | Today, developers circumvent the `shouldComponentUpdate` problem using 86 | subscriptions: 87 | 88 | * The provider acts as an event emitter. It keeps track of the most recent 89 | context value, and a list of subscribers to be notified whenever it changes. 90 | * The consumer accesses the provider's event emitter using the context API. 91 | (This usage is fine because the event emitter itself does not change). 92 | * The consumer registers an event listener with the provider. 93 | * When the provider emits a change event, the consumer is notified and calls 94 | `setState` on itself to schedule a re-render. 95 | 96 | Subscriptions are widely used by open source libraries such as Redux and React 97 | Broadcast. It works, but it has some clear drawbacks: 98 | 99 | * Not ergonomic. Given how common the context use case is, it shouldn't be so 100 | difficult to implement properly. 101 | * Start-up cost. Setting up subscriptions for every consumer is costly, 102 | especially since they aren't used during the initial mount. 103 | * Encourages mutation and other non-idiomatic patterns that could cause bugs in 104 | async mode. 105 | * The same code ends up being duplicated by every library, increasing bundle 106 | sizes. 107 | 108 | The meta problem is that the ownership and responsibility for a core feature has 109 | been shifted from the framework to its users. 110 | 111 | ## Main goals of this proposal 112 | 113 | * Zero cost (or close to it) for initial mount, commit, and unmount, trading-off 114 | update cost as necessary. 115 | * Easy-to-use API. 116 | * Statically typable. 117 | * Encourage async-friendly practices, like immutability. 118 | * Discourage non-ideal practices, like event emitters and mutation. 119 | * Eliminate duplicated complexity in userland code. 120 | 121 | # Detailed design 122 | 123 | Introduces new component types: `Provider` and `Consumer`. 124 | 125 | ```js 126 | type Provider = React.Component<{ 127 | value: T, 128 | children?: React.Node, 129 | }>; 130 | 131 | type Consumer = React.Component<{ 132 | children: (value: T) => React.Node, 133 | }>; 134 | ``` 135 | 136 | Providers and consumers come in pairs—for each provider, there is a 137 | corresponding consumer. 138 | 139 | A provider-consumer pair is created using `React.createContext()`: 140 | 141 | ```js 142 | type Context = { 143 | Provider: Provider, 144 | Consumer: Consumer, 145 | }; 146 | 147 | interface React { 148 | createContext(defaultValue: T): Context; 149 | } 150 | ``` 151 | 152 | `createContext` requires a default value to ensure type correctness. 153 | 154 | Note that an arbitrary provider and arbitrary consumer cannot necessarily be 155 | used in conjunction, even if their value type is the same. They must be the 156 | result of the same `createContext()` call. 157 | 158 | The provider accepts a context value as a prop. Any matching consumer in the 159 | provider's subtree can access it, regardless of how deeply it's nested. 160 | 161 | ```js 162 | render() { 163 | return ( 164 | 165 | {this.props.children} 166 | 167 | ); 168 | } 169 | ``` 170 | 171 | To update the context value, the parent re-renders and passes a different value. 172 | Changes to context are detected using `Object.is` comparison. (Referred to as 173 | strict comparision in this proposal, though `Object.is` is different from `===` 174 | in some edge cases.) This is meant to encourage the use of immutable or 175 | persistent data structures. In the typical scenario, context is updated by 176 | calling `setState` on the provider's parent. 177 | 178 | The consumer uses a render prop API: 179 | 180 | ```js 181 | render() { 182 | return ( 183 | 184 | {contextValue => } 185 | 186 | ) 187 | } 188 | ``` 189 | 190 | Notice how in the above example, the context value can be passed to any 191 | arbitrary prop on the child component. The advantage of the render prop API is 192 | that we avoid clobbering the prop namespace. 193 | 194 | If a consumer is rendered without a matching provider as its ancestor, it 195 | receives the default value passed to `createContext`, ensuring type safety. 196 | 197 | ## Implementation 198 | 199 | TODO. 200 | 201 | # Drawbacks 202 | 203 | ## Relies on strict comparison of context values 204 | 205 | This proposal uses strict (reference) comparison to detect changes to context 206 | values. This is partly to encourage the use of immutable or persistent data 207 | structures. But many common data sources rely on mutation. For example, certain 208 | implementations of Flux, or even newer libraries like Relay Modern. 209 | 210 | However, there are inherent problems with mutation when combined with async 211 | rendering, mostly related to tearing. For architectures that rely on mutation, 212 | developers will either decide some level of tearing is acceptable, or evolve to 213 | better support async. Regardless, these problems are not exclusive to the 214 | context API. 215 | 216 | An escape hatch for libraries that rely on mutation is to clone the outer 217 | container. (Or even just alternate between two copies.) React will detect a new 218 | object reference and trigger a change. 219 | 220 | ## Only one provider type per consumer 221 | 222 | The proposed API only allows for a consumer to read values from a single 223 | provider type, unlike the current API, which allows a consumer to read from an 224 | arbitrary number of provider types. 225 | 226 | The solution is to use compose consumers: 227 | 228 | ```js 229 | 230 | {foo => ( 231 | 232 | {bar => ( 233 | // Render using both foo and bar 234 | 235 | )} 236 | 237 | )} 238 | ; 239 | ``` 240 | 241 | Most abstractions around context already use similar patterns. 242 | 243 | # Alternatives 244 | 245 | ## setContext 246 | 247 | Instead of relying on reference equality to detect changes to context, we could 248 | instead use a `setContext` API that works like `setState`. However, setting 249 | aside the implementation overhead, this API would only be valuable when combined 250 | with mutation, which we're specifically aiming to discourage. 251 | 252 | ## Passing context to shouldComponentUpdate 253 | 254 | One argument is that we could avoid the `shouldComponentUpdate` problem by 255 | passing context as an argument to that method, compare the incoming context to 256 | the previous context, and return true if they are different. The problem is 257 | that, unlike props or state, we have no type information. The type of the 258 | context object depends on the component's position in the React tree. You could 259 | perform a shallow comparison of both objects, but that only works if we assume 260 | the values are immutable. And if we're going to assume the values are immutable, 261 | React might as well do the comparison automatically. 262 | 263 | ## Class-based API 264 | 265 | Instead of render props, we could use a class-based API similar to the one we 266 | have today: 267 | 268 | ```js 269 | class ThemeToggler extends React.Component { 270 | state = {theme: 'light'}; 271 | getChildContext() { 272 | return this.state.theme; 273 | } 274 | render() { 275 | return ( 276 | <> 277 | 285 | {this.props.children} 286 | 287 | ); 288 | } 289 | } 290 | 291 | class Title extends React.Component { 292 | static contextType = ThemeContext; 293 | componentDidUpdate(prevProps, prevState, prevContext) { 294 | if (this.context !== prevContext) { 295 | alert('Theme changed!'); 296 | } 297 | } 298 | render() { 299 | return ( 300 |

301 | {this.props.children} 302 |

303 | ); 304 | } 305 | } 306 | ``` 307 | 308 | The advantage of this API is you'd have easier access to context inside 309 | lifecycle methods, possibly avoiding the need for an extra component in the 310 | tree. 311 | 312 | However, although increasing the depth of a React tree incurs some overhead, the 313 | advantage of using special component types is that it's faster to scan the tree 314 | for consumers, because we can quickly skip over the other types. With the 315 | class-based API, we'd have to check every class component, which is slightly 316 | slower. This is likely enough to offset the cost of the extra component. 317 | 318 | Unlike the class API, the render prop API also has the advantage of being 319 | sufficiently different from the existing context API that we could support both 320 | versions during a transitional period without too much confusion. 321 | 322 | ## Algebraic effects 323 | 324 | In the past, we've considered modeling context in terms of 325 | [algebraic effects](https://github.com/reactjs/react-basic#algebraic-effects). 326 | Our conclusion was that memoizing effects (in order to skip over subtrees 327 | without reevaluating them) made this approach intractable. 328 | 329 | # How we teach this 330 | 331 | This would be our first API that uses render props. They are an increasingly 332 | popular pattern in third-party React libraries. However, there could be learning 333 | curve for beginners. 334 | 335 | Other than that, the use case is one that most React developers will be familiar 336 | with. We'll update the context documentation to use the new API. 337 | 338 | # Plan for adoption 339 | 340 | Before releasing, reach out to prominent third-party library maintainers. 341 | 342 | Initially, we will release the new API alongside the existing context API as a 343 | minor update. The APIs are different enough to avoid much confusion. 344 | 345 | Publish a migration guide. Make it clear that although the APIs are different, 346 | the new version of context provides a superset of functionality. Sending down 347 | static values is still supported. 348 | 349 | Replacing subscription-based patterns in favor of context's built-in change 350 | propgation may require a larger effort. However, as a first step, developers can 351 | migrate to the new API without abandoning subscriptions. Or, they can keep 352 | using subscriptions even with the new API, if that makes the most sense for 353 | their use case. 354 | 355 | Coordinate with library authors to migrate to the new API. Once the major 356 | libraries are ready, and we've allowed enough time for users to migrate, we will 357 | deprecate the old API in a minor update. 358 | 359 | In the following major release, we'll remove the old API. 360 | 361 | # Unresolved questions 362 | 363 | ## Should we warn if a consumer is rendered without a matching provider? 364 | 365 | There are valid use cases for relying on the default context value. In many 366 | or most cases, it's likely a developer mistake. We could print a warning in 367 | development when a consumer is rendered without a matching provider. To supress 368 | the warning, the developer passes `true` to `allowDetached`. 369 | 370 | ```js 371 | render() { 372 | return ( 373 | // If there's no provider, this renders with the default theme. 374 | // `allowDetached` suppresses the development warning 375 | 376 | {theme => ( 377 |

378 | {this.props.children} 379 |

380 | )} 381 |
382 | ); 383 | } 384 | ``` 385 | 386 | ## Add displayName argument to createContext for better debugging 387 | 388 | For warnings and React DevTools, it would help if providers and consumers 389 | had a `displayName`. The question is whether it should be required. We could 390 | make it optional, and use a Babel transform to add the name automatically. This 391 | is the strategy we use for `createClass`. 392 | 393 | ## Other 394 | 395 | * Should the consumer use `children` as a render prop, or a named prop? 396 | * How quickly should we deprecate and remove the existing context API? 397 | * Need to run benchmarks to determine how fast this will be. 398 | * Heuristics for caching. 399 | * A high-priority version of this feature for animations. (May submit as a 400 | separate proposal.) 401 | -------------------------------------------------------------------------------- /text/0006-static-lifecycle-methods.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2017-12-08 2 | - RFC PR: https://github.com/reactjs/rfcs/pull/6 3 | - React Issue: https://github.com/facebook/react/pull/12028 4 | 5 | # Summary 6 | 7 | Replace error-prone render phase lifecycle hooks with static methods to make it easier to write async-compatible React components. 8 | 9 | Provide a clear migration path for legacy components to become async-ready. 10 | 11 | # Basic example 12 | 13 | At a high-level, I propose the following additions/changes to the component API. (The motivations for these changes are explained below.) 14 | 15 | ```js 16 | class ExampleComponent extends React.Component { 17 | static getDerivedStateFromProps(nextProps, prevState) { 18 | // Called after a component is instantiated or before it receives new props. 19 | // Return an object to update state in response to prop changes. 20 | // Return null to indicate no change to state. 21 | } 22 | 23 | UNSAFE_componentWillMount() { 24 | // New name for componentWillMount() 25 | // Indicates that this method can be unsafe for async rendering. 26 | // Prefer componentDidMount() instead. 27 | } 28 | 29 | UNSAFE_componentWillUpdate(nextProps, nextState) { 30 | // New name for componentWillUpdate() 31 | // Indicates that this method can be unsafe for async rendering. 32 | // Prefer componentDidUpdate() instead. 33 | } 34 | 35 | UNSAFE_componentWillReceiveProps(nextProps) { 36 | // New name for componentWillReceiveProps() 37 | // Indicates that this method can be unsafe for async rendering. 38 | // Prefer static getDerivedStateFromProps() instead. 39 | } 40 | } 41 | ``` 42 | 43 | # Motivation 44 | 45 | The React team recently added a feature flag to stress-test Facebook components for potential incompatibilities with our experimental async rendering mode ([facebook/react/pull/11587](https://github.com/facebook/react/pull/11587)). We enabled this feature flag internally so that we could: 46 | 1. Identify common problematic coding patterns with the legacy component API to inform a new async component API. 47 | 2. Find and fix async bugs before they impact end-users by intentionally triggering them in a deterministic way. 48 | 3. Gain confidence that our existing products could work in async. 49 | 50 | I believe this internal experiment confirmed what we suspected about the legacy component API: _It has too many potential pitfalls to be safely used for async rendering._ 51 | 52 | ## Common problems 53 | 54 | Some of the most common problematic patterns that were uncovered include: 55 | * **Initializing Flux stores in `componentWillMount`**. It's often unclear whether this is an actual problem or just a potential one (eg if the store or its dependencies change in the future). Because of this uncertainty, it should be avoided. 56 | * **Adding event listeners/subscriptions** in `componentWillMount` and removing them in `componentWillUnmount`. This causes leaks if the initial render is interrupted (or errors) before completion. 57 | * **Non-idempotent external function calls** during `componentWillMount`, `componentWillUpdate`, `componentWillReceiveProps`, or `render` (eg registering callbacks that may be invoked multiple times, initializing or configuring shared controllers in such a way as to trigger invariants, etc.) 58 | 59 | ## Goal 60 | 61 | The goal of this proposal is to reduce the risk of writing async-compatible React components. I believe that can be accomplished by removing many1 of the potential pitfalls in the current API while retaining important functionality the API enables. This can be done through a combination of: 62 | 63 | 1. Choosing lifecycle method names that have a clearer, more limited purpose. 64 | 2. Making certain lifecycles static to prevent unsafe access of instance properties. 65 | 66 | 1 It is not possible to detect or prevent all side-effects (eg mutations of global/shared objects). 67 | 68 | ## Examples 69 | 70 | Let's look at some of the common usage patterns mentioned above and how they might be adapted to the new proposed API. 71 | 72 | ### Prefetching async data during mount 73 | 74 | The purpose of this pattern is to initiate data loading as early as possible. Particularly on slower, mobile devices, there may be several hundred miliseconds between the initial call to `render` and the subsequent `componentDidMount`. This pattern eagerly fetches data without waiting for the did-mount callback. 75 | 76 | It is worth noting that in both examples below, the data will not finish loading before the initial render and so a second render pass will be required in either case. 77 | 78 | #### Before 79 | 80 | ```js 81 | class ExampleComponent extends React.Component { 82 | state = { 83 | externalData: null, 84 | }; 85 | 86 | componentWillMount() { 87 | asyncLoadData(this.props.someId).then(externalData => 88 | this.setState({ externalData }) 89 | ); 90 | } 91 | 92 | render() { 93 | if (this.state.externalData === null) { 94 | // Render loading UI... 95 | } else { 96 | // Render real view... 97 | } 98 | } 99 | } 100 | ``` 101 | 102 | #### After 103 | 104 | ```js 105 | class ExampleComponent extends React.Component { 106 | state = { 107 | externalData: null, 108 | }; 109 | 110 | componentDidMount() { 111 | // Wait for earlier pre-fetch to complete and update state. 112 | // (This assumes some kind of cache to avoid duplicate requests.) 113 | asyncLoadData(this.props.someId).then(externalData => { 114 | // Note that if the component unmounts before this request completes, 115 | // It will trigger a warning, "cannot update an unmounted component". 116 | // You can avoid this by tracking mounted state with an instance var if desired. 117 | this.setState({ externalData }); 118 | }); 119 | } 120 | 121 | render() { 122 | if (this.state.externalData === null) { 123 | // Prime an external cache as early as possible. 124 | // (Async request would not complete before render anyway.) 125 | asyncLoadData(this.props.someId); 126 | 127 | // Render loading UI... 128 | } else { 129 | // Render real view... 130 | } 131 | } 132 | } 133 | ``` 134 | 135 | ### State derived from props/state 136 | 137 | The purpose of this pattern is to calculate some values derived from props for use during render. 138 | 139 | Typically `componentWillReceiveProps` is used for this, although if the calculation is fast enough it could just be done in `render`. 140 | 141 | #### Before 142 | 143 | ```js 144 | class ExampleComponent extends React.Component { 145 | state = { 146 | derivedData: computeDerivedState(this.props) 147 | }; 148 | 149 | componentWillReceiveProps(nextProps) { 150 | if (this.props.someValue !== nextProps.someValue) { 151 | this.setState({ 152 | derivedData: computeDerivedState(nextProps) 153 | }); 154 | } 155 | } 156 | } 157 | ``` 158 | 159 | #### After 160 | 161 | ```js 162 | class ExampleComponent extends React.Component { 163 | // Initialize state in constructor, 164 | // Or with a property initializer. 165 | state = {}; 166 | 167 | static getDerivedStateFromProps(nextProps, prevState) { 168 | if (prevState.someMirroredValue !== nextProps.someValue) { 169 | return { 170 | derivedData: computeDerivedState(nextProps), 171 | someMirroredValue: nextProps.someValue 172 | }; 173 | } 174 | 175 | // Return null to indicate no change to state. 176 | return null; 177 | } 178 | } 179 | ``` 180 | 181 | ### Adding event listeners/subscriptions 182 | 183 | The purpose of this pattern is to subscribe a component to external events when it mounts and unsubscribe it when it unmounts. 184 | 185 | The `componentWillMount` lifecycle is often used for this purpose, but this is problematic because any interruption _or_ error during initial mount will cause a memory leak. (The `componentWillUnmount` lifecycle hook is not invoked for a component that does not finish mounting and so there's no safe place to handle unsubscriptions in that case.) 186 | 187 | Using `componentWillMount` for this purpose might also cause problems in the context of server-rendering. 188 | 189 | #### Before 190 | 191 | ```js 192 | class ExampleComponent extends React.Component { 193 | componentWillMount() { 194 | this.setState({ 195 | subscribedValue: this.props.dataSource.value 196 | }); 197 | 198 | // This is not safe; (it can leak). 199 | this.props.dataSource.subscribe(this._onSubscriptionChange); 200 | } 201 | 202 | componentWillUnmount() { 203 | this.props.dataSource.unsubscribe(this._onSubscriptionChange); 204 | } 205 | 206 | render() { 207 | // Render view using subscribed value... 208 | } 209 | 210 | _onSubscriptionChange = subscribedValue => { 211 | this.setState({ subscribedValue }); 212 | }; 213 | } 214 | ``` 215 | 216 | #### After 217 | 218 | ```js 219 | class ExampleComponent extends React.Component { 220 | state = { 221 | subscribedValue: this.props.dataSource.value 222 | }; 223 | 224 | componentDidMount() { 225 | // Event listeners are only safe to add after mount, 226 | // So they won't leak if mount is interrupted or errors. 227 | this.props.dataSource.subscribe(this._onSubscriptionChange); 228 | 229 | // External values could change between render and mount, 230 | // In some cases it may be important to handle this case. 231 | if (this.state.subscribedValue !== this.props.dataSource.value) { 232 | this.setState({ 233 | subscribedValue: this.props.dataSource.value 234 | }); 235 | } 236 | } 237 | 238 | componentWillUnmount() { 239 | this.props.dataSource.unsubscribe(this._onSubscriptionChange); 240 | } 241 | 242 | render() { 243 | // Render view using subscribed value... 244 | } 245 | 246 | _onSubscriptionChange = subscribedValue => { 247 | this.setState({ subscribedValue }); 248 | }; 249 | } 250 | ``` 251 | 252 | ### External function calls (side effects, mutations) 253 | 254 | The purpose of this pattern is to send an external signal that something has changed internally (eg in `state`). 255 | 256 | The `componentWillUpdate` lifecycle hook is sometimes used for this but it is not ideal because this method may be called multiple times _or_ called for props that are never committed. `componentDidUpdate` should be used for this purpose rather than `componentWillUpdate`. 257 | 258 | #### Before 259 | 260 | ```js 261 | class ExampleComponent extends React.Component { 262 | componentWillUpdate(nextProps, nextState) { 263 | if (this.state.someStatefulValue !== nextState.someStatefulValue) { 264 | nextProps.onChange(nextState.someStatefulValue); 265 | } 266 | } 267 | } 268 | ``` 269 | 270 | #### After 271 | 272 | ```js 273 | class ExampleComponent extends React.Component { 274 | componentDidUpdate(prevProps, prevState) { 275 | // Callbacks (side effects) are only safe after commit. 276 | if (this.state.someStatefulValue !== prevState.someStatefulValue) { 277 | this.props.onChange(this.state.someStatefulValue); 278 | } 279 | } 280 | } 281 | ``` 282 | 283 | ### Memoized values derived from `props` and/or `state` 284 | 285 | The purpose of this pattern is to memoize computed values based on `props` and/or `state`. 286 | 287 | Typically such values are stored in `state`, but in some cases the values require mutation and as such may not seem suited for state (although they could technically still be stored there). An example of this would be an external helper class that calculates and memoizes values internally. 288 | 289 | In other cases the value may be derived from `props` _and_ `state`. 290 | 291 | #### Before 292 | 293 | ```js 294 | class ExampleComponent extends React.Component { 295 | componentWillMount() { 296 | this._calculateMemoizedValues(this.props, this.state); 297 | } 298 | 299 | componentWillUpdate(nextProps, nextState) { 300 | if ( 301 | this.props.someValue !== nextProps.someValue || 302 | this.state.someOtherValue !== nextState.someOtherValue 303 | ) { 304 | this._calculateMemoizedValues(nextProps, nextState); 305 | } 306 | } 307 | 308 | render() { 309 | // Render view using calculated memoized values... 310 | } 311 | } 312 | ``` 313 | 314 | #### After 315 | 316 | ```js 317 | class ExampleComponent extends React.Component { 318 | render() { 319 | // Memoization that doesn't go in state can be done in render. 320 | // It should be idempotent and have no external side effects or mutations. 321 | this._calculateMemoizedValues(this.props, this.state); 322 | 323 | // Render view using calculated memoized values... 324 | } 325 | } 326 | ``` 327 | 328 | ### Initializing Flux stores during mount 329 | 330 | The purpose of this pattern is to initialize some Flux state when a component is mounted. 331 | 332 | This is sometimes done in `componentWillMount` which can be problematic if, for example, the action is not idempotent. From the point of view of the component, it's often unclear whether dispatching the action more than once will cause a problem. It's also possible that it does not cause a problem when the component is authored but later does due to changes in the store. Because of this uncertainty, it should be avoided. 333 | 334 | We recommend using `componentDidMount` for such actions since it will only be invoked once. 335 | 336 | #### Before 337 | 338 | ```js 339 | class ExampleComponent extends React.Component { 340 | componentWillMount() { 341 | FluxStore.dispatchSomeAction(); 342 | } 343 | } 344 | 345 | ``` 346 | 347 | #### After 348 | 349 | ```js 350 | class ExampleComponent extends React.Component { 351 | componentDidMount() { 352 | // Side effects (like Flux actions) should only be done after mount or update. 353 | // This prevents duplicate actions or certain types of infinite loops. 354 | FluxStore.dispatchSomeAction(); 355 | } 356 | } 357 | ``` 358 | 359 | # Detailed design 360 | 361 | ## New static lifecycle methods 362 | 363 | ### `static getDerivedStateFromProps(nextProps: Props, prevState: State): $Shape | null` 364 | 365 | This method is invoked after a component is instantiated and when it receives new props. Return an object to update state in response to prop changes. Return null to indicate no change to state. 366 | 367 | If an object is returned, its keys will be merged into the existing state. 368 | 369 | Note that React may call this method even if the props have not changed. If calculating derived data is expensive, compare next and previous props to conditionally handle changes. 370 | 371 | ## Deprecated lifecycle methods 372 | 373 | ### `componentWillMount` -> `UNSAFE_componentWillMount` 374 | 375 | This method is deprecated and will be removed in the next major version. Read about the motivations behind this change at [fb.me/react-async-component-lifecycle-hooks](https://fb.me/react-async-component-lifecycle-hooks) 376 | 377 | As a temporary workaround, you can rename to `UNSAFE_componentWillMount` instead. 378 | 379 | ### `componentWillUpdate` -> `UNSAFE_componentWillUpdate` 380 | 381 | This method is deprecated and will be removed in the next major version. Read about the motivations behind this change at [fb.me/react-async-component-lifecycle-hooks](https://fb.me/react-async-component-lifecycle-hooks) 382 | 383 | As a temporary workaround, you can rename to `UNSAFE_componentWillUpdate` instead. 384 | 385 | ### `componentWillReceiveProps` -> `UNSAFE_componentWillReceiveProps` 386 | 387 | This method is deprecated and will be removed in the next major version. Use `static getDerivedStateFromProps()` instead. Read about the motivations behind this change at [fb.me/react-async-component-lifecycle-hooks](https://fb.me/react-async-component-lifecycle-hooks) 388 | 389 | As a temporary workaround, you can rename to `UNSAFE_componentWillReceiveProps` instead. 390 | 391 | # Drawbacks 392 | 393 | The current component lifecycle hooks are familiar and used widely. This proposed change will introduce a lot of churn within the ecosystem. I hope that we can reduce the impact of this change through the use of codemods, but it will still require a manual review process and testing. 394 | 395 | This change is **not fully backwards compatible**. Libraries will need to drop support for older versions of React in order to use the new, static API. Unfortunately, I believe this is unavoidable in order to safely transition to an async-compatible world. 396 | 397 | # Alternatives 398 | 399 | ## Try to detect problems using static analysis 400 | 401 | It is possible to create ESLint rules that attempt to detect and warn about potentially unsafe actions inside of render-phase lifecycle hooks. Such rules would need to be very strict though and would likely result in many false positives. It would also be difficult to ensure that library maintainers correctly used these lint rules, making it possible for async-unsafe components to cause problems within an async tree. 402 | 403 | Sebastian has also discussed the idea of side effect tracking with the Flow team. Conceptually this would enable us to know, statically, whether a method is free of side effects and mutations. This functionality does not currently exist in Flow though, and if it did there will still be an adoption problem. (Not everyone uses Flow and there's no way to guarantee the shared components you rely on are safe.) 404 | 405 | ## Don't support async with the legacy class component API 406 | 407 | We could leave the class component API as-is and instead focus our efforts on a new stateful, functional component API. If a legacy class component is detected within an async tree, we could revert to sync rendering mode. 408 | 409 | There are no advanced proposals for such a stateful, functional component API that I'm aware of however, and the complexity of such a migration would likely be at least as large as this proposal. 410 | 411 | # Adoption strategy 412 | 413 | Begin by reaching out to prominent third-party library maintainers to make sure there are no use-cases we have failed to consider. 414 | 415 | Assuming we move forward with the proposal, release (at least one) minor 16.x update to add deprecation warnings for the legacy lifecycles and inform users to either rename with the `UNSAFE_` prefix or use the new static methods instead. We'll then cordinate with library authors to ensure they have enough time to migrate to the new API in advance of the major release that drops support for the legacy lifecycles. 416 | 417 | We will provide a codemod to rename the deprecated lifecycle hooks with the new `UNSAFE_` prefix. 418 | 419 | We will also provide codemods to assist with the migration to static methods, although given the nature of the change, codemods will be insufficient to handle all cases. Manual verification will be required. 420 | 421 | # How we teach this 422 | 423 | Write a blog post (or a series of posts) announcing the new lifecycle hooks and explaining our motivations for the change, as well as the benefits of being async-compatible. Provide examples of how to migrate the most common legacy patterns to the new API. (This can be more detailed versions of the [basic example](#basic-example) shown in the beginning of this RFC.) 424 | 425 | # Unresolved questions 426 | 427 | ## Can `shouldComponentUpdate` remain an instance method? 428 | 429 | Anectdotally, it seems far less common for this lifecycle hook to be used in ways that are unsafe for async. The overwhelming common usage of it seems to be returning a boolean value based on the comparison of current to next props. 430 | 431 | On the one hand, this means the method could be easily codemodded to a static method, but it would be equally easy to write a custom ESLint rule to warn about `this` references to anything other than `this.props` inside of `shouldComponentUpdate`. 432 | 433 | Beyond this, there is some concern that making this method static may complicate inheritance for certain languages/compilers. 434 | 435 | ## Can `render` remain an instance method? 436 | 437 | There primary motivation for leaving `render` as an instance method is to allow other instance methods to be used as event handlers and ref callbacks. (It is important for event handlers to be able to call `this.setState`.) We may change the event handling API in the future to be compatible with eg error boundaries, at which point it might be appropriate to revisit this decision. 438 | 439 | Leaving `render` as an instance method also provides a mechanism (other than `state`) on which to store memoized data. 440 | 441 | ## Other 442 | 443 | Are there important use cases that I've overlooked that the new static lifecycles would be insufficient to handle? 444 | -------------------------------------------------------------------------------- /text/0017-new-create-ref.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2018-01-25 2 | - RFC PR: https://github.com/reactjs/rfcs/pull/17 3 | - React Issue: https://github.com/facebook/react/issues/10581 4 | 5 | # Summary 6 | 7 | Currently there are two ref APIs in React: string refs and callback refs. 8 | 9 | String refs are considered legacy due to [numerous issues](https://github.com/facebook/react/issues/1373) in their design. [Callback refs](https://reactjs.org/docs/refs-and-the-dom.html) don't share these deficiencies and were introduced to replace string refs. However, there is more ceremony around writing them, they have some [unintuitive caveats](https://reactjs.org/docs/refs-and-the-dom.html#caveats-with-callback-refs) and can be hard to teach. 10 | 11 | **The goal of this RFC is to introduce a new intuitive ref API. It is very similar in its "feel" to string refs, but doesn't suffer from their problems.** It is intentionally less poweful than the callback ref API. The plan is to: 12 | 13 | * Keep the callback ref API for more advanced use cases. 14 | * Replace the string refs by the newly proposed "object" ref API. 15 | 16 | String refs would get deprecated in a minor release, and support for them would eventually get removed in an upcoming major release. 17 | 18 | # Basic example 19 | 20 | The `React.createRef()` API will create an immutable object ref (where its value is a mutable object referencing the actual ref). Accessing the ref value can be done via `ref.current`. An example of how this works is below: 21 | 22 | ```js 23 | class MyComponent extends React.Component { 24 | divRef = React.createRef(); 25 | 26 | render() { 27 | return
; 28 | } 29 | 30 | componentDidMount() { 31 | this.divRef.current.focus(); 32 | } 33 | } 34 | ``` 35 | 36 | # Motivation 37 | 38 | The primary motivation is to encourage people to migrate off string refs. Callback refs meet some resistance because they are a bit harder to understand. The proposal introduces this API primarily for people who love string refs today. 39 | 40 | ### What's wrong with string refs? 41 | 42 | The main problem with string refs is that they require React to keep track of the "currently executing" component. However, that component doesn't always match where the string ref is defined. Consider the common "render prop" pattern: 43 | 44 | ```js 45 | class ComponentA { 46 | render() { 47 | return ( 48 | // ref foo1 would bind to ComponentA 49 |
50 | { 51 | // ref foo2 would bind to ComponentB 52 | // even though it's all in ComponentA's render 53 | () =>
54 | } 55 |
56 | ); 57 | } 58 | } 59 | ``` 60 | 61 | This behavior is highly confusing. 62 | 63 | There are other issues with string refs in practice: 64 | 65 | * They cause bugs when [multiple React copies are on the same page](https://reactjs.org/warnings/refs-must-have-owner.html). 66 | * They're not friendly to static typing. 67 | * They aren't compatible with advanced compilation strategies that mangle class properties. 68 | * They can't be [composed](https://github.com/ide/react-clone-referenced-element/). 69 | 70 | ### Don't we have callback refs for this? 71 | 72 | Many people continue to favor string refs because writing callback refs requires more mental overhead (you have to think about a field *and* a function that sets it). They are especially inconvenient when you need an array of refs. Callback refs also have [unusual caveats](https://reactjs.org/docs/refs-and-the-dom.html#caveats-with-callback-refs) that are [often](https://github.com/facebook/react/issues/9328) [mistaken for a bug](https://github.com/facebook/react/issues/8619). 73 | 74 | Callback refs are, strictly saying, more powerful than either string refs or the proposed object refs. However, there is definitely a niche for a simpler, more convenient API for the majority of cases. There might be some small wins in performance too: commonly, ref value is assigned in a closure created in the render phase of a component. This API avoids that. Similarly, this avoids assigning to a non-existent component instance property, which also can cause deopts in JavaScript engines. 75 | 76 | # Detailed design 77 | 78 | Introduces `React.createRef()`: 79 | 80 | ```js 81 | type RefObject = { 82 | current: T | null 83 | }; 84 | 85 | interface React { 86 | createRef(): RefObject; 87 | } 88 | ``` 89 | 90 | `createRef` requires a explicit type annotation to ensure type correctness. 91 | 92 | Before `componentDidMount` lifecycle `divRef.current` will be filled with element/component reference: 93 | 94 | ```js 95 | componentDidMount() { 96 | this.divRef.current instanceof HTMLDivElement === true 97 | } 98 | 99 | render() { 100 | return ( 101 |
102 | {this.props.children} 103 |
104 | ); 105 | } 106 | ``` 107 | 108 | # Drawbacks 109 | 110 | Callback refs are easier to use when child with ref and parent have different lifetime. Consider this example: 111 | 112 | ```js 113 | class CallbackRefExample extends React.Component { 114 | // Callback refs offer a centralized place 115 | // to perform side effects. 116 | divRef = (div) => { 117 | if (div) { 118 | this.div = div; 119 | this.div.addEventListener('foo', onFoo); 120 | } else { 121 | this.div.removeEventListener('foo', onFoo); 122 | this.div = null; 123 | } 124 | } 125 | 126 | onFoo = () => { 127 | console.log('hello'); 128 | } 129 | 130 | render() { 131 | if (this.props.enabled) { 132 | return
; 133 | } else { 134 | return null; 135 | } 136 | } 137 | } 138 | ``` 139 | 140 | We could write an equivalent example with object refs by using `componentDidMount` and `componentDidUpdate`. However, by the time `componentDidUpdate` fires, we have no way to access the previous ref value to remove the listener. So we'd have to "mirror" the ref value in a separate instance variable. This becomes very tedious and error-prone. 141 | 142 | The conclusion here is that for advanced use cases (such as side effects during ref attachment and detachment), callback refs are preferable. Still, in most cases this is not necessary, and object refs are sufficient. 143 | 144 | # Alternatives 145 | 146 | One potential alternative is to do nothing, and keep string refs and callback refs. The problem with keeping string refs is that they prevent some future work on React, including advanced compilation strategies. So we need a migration path off of them. 147 | 148 | Another potential alternative is to fully embrace callback refs but deprecate string refs. However, there is a lot of feedback suggesting that people struggle with understanding and using the callback ref API, and a simpler option is necessary. 149 | 150 | The `createRef` API could potentially be implemented as a userland package. However, it would be a one-liner, and most people would not know about it. If we wanted everybody to use it, we might as well build it into React. 151 | 152 | # Adoption strategy 153 | 154 | Initially, we will release the object refs alongside the existing string and callback refs as a minor update. This update will also include a `` component which enables deprecation messages for its subtree. This lets people catch string refs usage in their components and replace with the new API (or callback refs, if they prefer) incrementally. 155 | 156 | String refs will be removed in an upcoming major release, so there will be enough time for migration. 157 | 158 | # How we teach this 159 | 160 | Callback and object refs solve different problems and crossing over in what they offer too - much like how components in React do currently. 161 | 162 | In most cases people would choose an object ref. They could use it when they only care about accessing the object reference. The ref object can be passed around, or stored in an array. 163 | 164 | Callback refs will be positioned as an advanced API. It is useful when someone likes to have fine-grained control over the node when it gets attached and detached, or to compose multiple refs. 165 | 166 | # Unresolved questions 167 | 168 | None. 169 | -------------------------------------------------------------------------------- /text/0030-ref-forwarding.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2018-03-07 2 | - RFC PR: https://github.com/reactjs/rfcs/pull/30 3 | - React Issue: https://github.com/facebook/react/pull/12346 4 | 5 | # Summary 6 | 7 | Provide an API that enables [`refs`](https://reactjs.org/docs/refs-and-the-dom.html) to be forwarded to a descendant (child or grandchild component). 8 | 9 | # Motivation 10 | 11 | I recently began contributing to [`react-relay` modern](https://github.com/facebook/relay/tree/master/packages/react-relay/modern) in order to prepare it for the [upcoming React async-rendering feature](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html). 12 | 13 | One of the first challenges I encountered was that `react-relay` depends on the legacy, unstable context API, and values passed through legacy `context` aren't accessible in the new, [static `getDerivedStateFromProps` lifecycle](https://github.com/reactjs/rfcs/blob/master/text/0006-static-lifecycle-methods.md#static-getderivedstatefrompropsnextprops-props-prevstate-state-shapestate--null). The long-term plan for `react-relay` is to use the [new and improved context API](https://github.com/reactjs/rfcs/blob/master/text/0002-new-version-of-context.md) but this can't be done without dropping support for older versions of React- (something `react-relay` may not be able to do yet). 14 | 15 | In an effort to work around this, I created a smaller wrapper component to convert the necessary `context` value to a `prop` so that it could be accessed within the static lifecycle: 16 | 17 | ```js 18 | function injectLegacyRelayContext(Component) { 19 | function LegacyRelayContextConsumer(props, legacyContext) { 20 | return ; 21 | } 22 | 23 | LegacyRelayContextConsumer.contextTypes = { 24 | relay: RelayPropTypes.Relay 25 | }; 26 | 27 | return LegacyRelayContextConsumer; 28 | } 29 | ``` 30 | 31 | Unfortunately, this change broke several projects within Facebook that depended on refs to access the inner Relay components. 32 | 33 | As I considered this, I became convinced that the new Context API naturally lends itself to similar wrapper components, e.g.: 34 | ```js 35 | const ThemeContext = React.createContext("light"); 36 | 37 | function withTheme(ThemedComponent) { 38 | return function ThemeContextInjector(props) { 39 | return ( 40 | 41 | {value => } 42 | 43 | ); 44 | }; 45 | } 46 | ``` 47 | 48 | Unfortunately, within the current limitations of React, there is no (transparent) way for a 49 | a `ref` to be attached to the above `ThemedComponent`. Common workarounds typically use a special prop (e.g. `componentRef`) like so: 50 | ```js 51 | const ThemeContext = React.createContext("light"); 52 | 53 | function withTheme(ThemedComponent) { 54 | return function ThemeContextInjector(props) { 55 | return ( 56 | 57 | {value => ( 58 | 59 | )} 60 | 61 | ); 62 | }; 63 | } 64 | ``` 65 | 66 | This convention varies from project to project, and requires users of the `ThemedComponent` to be aware of the fact that it's wrapped by a higher-order component. This detail should not be important. It should be possible to use a standard React `ref` that the `ThemeContextInjector` (in this case) could forward to its child `ThemedComponent`. 67 | 68 | The idea of `ref`-forwarding [is not new](https://github.com/facebook/react/issues/4213), but the new context and other efforts like [`create-subscription`](https://github.com/facebook/react/pull/12325) greatly increase the importance of this feature. 69 | 70 | # Basic example 71 | 72 | The Relay `context` injector component ([shown above](#motivation)) could use the ref-forwarding API proposed by this RFC as follows: 73 | 74 | ```js 75 | function injectLegacyRelayContext(Component) { 76 | function LegacyRelayContextConsumer(props, legacyContext) { 77 | return ( 78 | 83 | ); 84 | } 85 | 86 | LegacyRelayContextConsumer.contextTypes = { 87 | relay: RelayPropTypes.Relay 88 | }; 89 | 90 | // Create a special React component type that exposes the external 'ref'. 91 | // This lets us pass it along as a regular prop, 92 | // And attach it as a regular React 'ref' on the component we choose. 93 | return React.forwardRef((props, ref) => ( 94 | 95 | )); 96 | } 97 | ``` 98 | 99 | The theme context wrapper could as well: 100 | 101 | ```js 102 | const ThemeContext = React.createContext("light"); 103 | 104 | function withTheme(ThemedComponent) { 105 | function ThemeContextInjector(props) { 106 | return ( 107 | 108 | {value => ( 109 | 110 | )} 111 | 112 | ); 113 | } 114 | 115 | // Forward refs through to the inner, "themed" component: 116 | return React.forwardRef((props, ref) => ( 117 | 118 | )); 119 | } 120 | ``` 121 | 122 | Here is another example [Dan mentioned on Twitter](https://twitter.com/dan_abramov/status/974008682311815169): 123 | 124 | ```js 125 | // Tell React I want to be able to put a ref on LikeButton. 126 | const LikeButton = React.forwardRef((props, ref) => ( 127 |
128 | {/* Forward LikeButton's ref to the button inside. */} 129 | 132 |
133 | )); 134 | 135 | // I can put a ref on it as if it was a DOM node or a class! 136 | // (In this example, the ref points directly to the DOM node.) 137 | ; 138 | ``` 139 | 140 | # Detailed design 141 | 142 | Hopefully the usage of this API is clear from the [above examples](#basic-example), so in this section I'll outline a possible implementation strategy. 143 | 144 | The `forwardRef` function could return a wrapper object (similar to what the context API uses) with a `$$typeof` indicating that it's a ref-forwarding component. When React encounters this type, it can assign a new type-of-work to React (e.g. `UseRef`). The "begin" phase could then invoke the render prop argument, passing it `workInProgress.pendingProps` and `workInProgress.ref` to create the children, and then continue reconciliation. 145 | 146 | # Drawbacks 147 | 148 | This API increases the surface area of React slightly, and may complicate compatibility efforts for react-like frameworks (e.g. `preact-compat`). I believe this is worth the benefit of having a standardized, transparent way to forward refs. 149 | 150 | # Alternatives 151 | 152 | 1. Add a new class method, e.g. `getPublicInstance` or `getWrappedInstance` that could be used to get the inner ref. (Some drawbacks listed [here](https://github.com/facebook/react/issues/4213#issuecomment-115019321).) 153 | 154 | 2. Specify a ["high-level" flag on the component](https://github.com/facebook/react/issues/4213#issuecomment-115048260) that instructs React to forward refs past it. This approach could enable refs to be forwarded one level (to the immediate child) but would not enable forwarding to deeper child, e.g.: 155 | 156 | ```js 157 | const ThemeContext = React.createContext("light"); 158 | 159 | function withTheme(ThemedComponent) { 160 | return function ThemeContextInjector(props) { 161 | return ( 162 | 163 | {value => ( 164 | // ref belongs here 165 | 166 | )} 167 | 168 | ); 169 | }; 170 | } 171 | ``` 172 | 173 | 3. [Automatically forward refs for stateless functions components](https://github.com/facebook/react/issues/4213#issuecomment-115051991). (React currently warns if you try attaching a `ref` to a functional component, since there is no backing instance to reference.) This approach would not enable class components to forward refs, and so would not be sufficient, since wrapper components often require class lifecycles. It would also have the same child-depth limitations as the above option. 174 | 175 | # Adoption strategy 176 | 177 | This is a new feature. Since there are no backwards-compatibility concerns, adoption can be organic. 178 | 179 | One candidate that would immediately benefit from this feature would be [`create-subscription`](https://github.com/facebook/react/pull/12325). 180 | 181 | # How we teach this 182 | 183 | Add a section to the ["Refs and the DOM" documentation page](https://reactjs.org/docs/refs-and-the-dom.html) about ref-forwarding. 184 | 185 | Write a blog post about the new feature and when/why you might want to use it. Highlight examples using the new context API. 186 | 187 | # Unresolved questions 188 | 189 | None presently. -------------------------------------------------------------------------------- /text/0033-new-commit-phase-lifecycles.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2018-03-10 2 | - RFC PR: https://github.com/reactjs/rfcs/pull/33 3 | - React Issue: https://github.com/facebook/react/issues/7678 4 | 5 | # Summary 6 | 7 | Add new "commit" phase lifecycle, `getSnapshotBeforeUpdate`, that gets called _before_ mutations are made. Any value returned by this lifecycle will be passed as the third parameter to `componentDidUpdate`. 8 | 9 | This lifecycle is important for [async rendering](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html), where there may be delays between "render" phase lifecycles (e.g. `componentWillUpdate` and `render`) and "commit" phase lifecycles (e.g. `componentDidUpdate`). 10 | 11 | # Basic example 12 | 13 | Consider the use case of preserving scroll position within a list as its contents are updated. The way this is typically done is to read `scrollHeight` during render (`componentWillUpdate`) and then adjust it after the update has been committed (`componentDidUpdate`). 14 | 15 | Unfortunately this approach **does not work with async rendering**, because there might be a delay between these lifecycles during which the user continues scrolling. The only way to ensure an accurate scroll position is read would be to _force a synchronous render_. 16 | 17 | The solution is to introduce a new lifecycle that gets called during the commit phase before mutations have been made to e.g. the DOM. For example: 18 | 19 | ```js 20 | type Snapshot = number | null; 21 | 22 | class ScrollingList extends React.Component { 23 | listRef = React.createRef(); 24 | 25 | getSnapshotBeforeUpdate( 26 | prevProps: Props, 27 | prevState: State 28 | ): Snapshot { 29 | // Are we adding new items to the list? 30 | // Capture the current height of the list so we can adjust scroll later. 31 | if (prevProps.list.length < this.props.list.length) { 32 | return this.listRef.value.scrollHeight; 33 | } 34 | 35 | return null; 36 | } 37 | 38 | componentDidUpdate( 39 | prevProps: Props, 40 | prevState: State, 41 | snapshot: Snapshot 42 | ) { 43 | // If we have a snapshot value, then we've just added new items. 44 | // Adjust scroll so these new items don't push the old ones out of view. 45 | if (snapshot !== null) { 46 | this.listRef.value.scrollTop += 47 | this.listRef.value.scrollHeight - snapshot; 48 | } 49 | } 50 | 51 | render() { 52 | return ( 53 |
{/* ...contents... */}
54 | ); 55 | } 56 | } 57 | ``` 58 | 59 | # Motivation 60 | 61 | This lifecycle provides a way for asynchronously rendered components to accurately read values from the host environment (e.g. the DOM) before it is mutated. 62 | 63 | The [example above](#basic-example) describes one use case in which this could be useful. Others might involve text selection and cursor position, audio/video playback position, etc. 64 | 65 | # Detailed design 66 | 67 | Add a new effect type, `Snapshot`, and update `ReactFiberClassComponent` to assign this type when updating components that define the new `getSnapshotBeforeUpdate` lifecycle. 68 | 69 | During the `commitAllHostEffects` traversal, call `getSnapshotBeforeUpdate` for any fiber tagged with the new `Snapshot` effect type. Store return value on the instance (as `__reactInternalSnapshotBeforeUpdate`) and later pass to `componentDidUpdate` during `commitLifeCycles`. 70 | 71 | ### New DEV warnings 72 | 73 | Add DEV warnings for the following conditions: 74 | * Undefined return values for `getSnapshotBeforeUpdate` 75 | * Components that define `getSnapshotBeforeUpdate` without also defining `componentDidUpdate` 76 | 77 | ### Flow 78 | 79 | Flow will also need to be updated to add a third `Snapshot` type parameter to `React.Component` to ensure consistency for the return type fo `getSnapshotBeforeUpdate` adn the new parameter passed to `componentDidUpdate`. This new type parameter will be declared like so: 80 | 81 | ```js 82 | // If there is a State type: 83 | class Example extends React.Component {} 84 | 85 | // If there is no State type: 86 | class Example extends React.Component {} 87 | ``` 88 | 89 | ### Polyfill support 90 | 91 | It would be possible for [react-lifecycles-compat](https://github.com/reactjs/react-lifecycles-compat) to polyfill this new method for older, synchronous versions of React using the `componentWillUpdate` lifecycle. It would require a couple of hacks though: 92 | * The polyfilled `componentWillUpdate` method would need to temporarily mutate instance props (`this.props` and `this.state`) before calling the new `getSnapshotFromUdate` method in order to maintain next/prev semantics. 93 | * The polyfill would need to mutate the component's `prototype` to decorate `componentDidUpdate` in order to add the new `snapshot` parameter. This would not work in all cases (e.g. methods that get attached to the instance in the constructor rather than as part of the prototype). 94 | 95 | Regardless, I think it's probably reasonable to follow the precedent set by `getDerivedStateFromProps` and _not_ call the call _unsafe_ legacy lifecycles `componentWillMount`, `componentWillReceiveProps`, or `componentWillUpdate` for any component that defines the new `getSnapshotBeforeUpdate` method. 96 | 97 | A DEV warning can be added for components that define both `getSnapshotBeforeUpdate` and any of the unsafe legacy lifecycles. 98 | 99 | # Drawbacks 100 | 101 | Each new lifecycle adds complexity and makes the component API harder for beginners to understand. Although this lifecycle _is important_, it will probably _not be used often_, and so I think the impact is minimal. 102 | 103 | # Alternatives 104 | 105 | A new commit-phase lifecycle is necessary. The signature does not have to match the one proposed by this RFC however. Below are some alternatives that were considered. 106 | 107 | ### Static method 108 | 109 | The most recently-added lifecycle, `getDerivedStateFromProps`, was a static method in order to prevent unsafe access of instance properties. That concern is less relevant in this case though, because this lifecycle is called during the commit phase. 110 | 111 | ```js 112 | class ScrollingList extends React.Component { 113 | state = { 114 | listHasGrown: false, 115 | listRef: React.createRef(), 116 | prevList: this.props.list 117 | }; 118 | 119 | static getDerivedStateFromProps( 120 | nextProps: Props, 121 | prevState: State 122 | ): $Shape | null { 123 | if (nextProps.list !== prevState.prevList) { 124 | return { 125 | listHasGrown: 126 | nextProps.list.length > prevState.prevList.length, 127 | prevList: nextProps.list 128 | }; 129 | } else if (prevState.listHasGrown) { 130 | return { 131 | listHasGrown: false 132 | }; 133 | } 134 | 135 | return null; 136 | } 137 | 138 | static getSnapshotBeforeUpdate( 139 | prevProps: Props, 140 | prevState: State 141 | ): Snapshot | null { 142 | if (prevState.listHasGrown) { 143 | return prevState.listRef.value.scrollHeight; 144 | } 145 | 146 | return null; 147 | } 148 | 149 | // ... 150 | } 151 | ``` 152 | 153 | This approach was not chosen because of the added complexity of storing additional values (including refs) in `state`. 154 | 155 | ### No return value 156 | 157 | The proposed lifecycle will be the first commit phase lifecycle with a meaningful return value and the first lifecycle whose return value is passed as a parameter to another lifecycle. Likewise, the new parameter for `componentDidUpdate` will be the first passed to a lifecycle that isn't some form of `Props` or `State`. This adds some complexity to the API, since it requires a more nuanced understanding the relationship between `getSnapshotBeforeUpdate` and `componentDidUpdate`. 158 | 159 | An alternative would be to scrap the return value in favor of storing snapshot values on the instance. This has the added benefit of not requiring any changes to be made to Flow. 160 | 161 | ```js 162 | class ScrollingList extends React.Component { 163 | listRef = React.createRef(); 164 | listScrollHeight = null; 165 | 166 | getSnapshotBeforeUpdate( 167 | prevProps: Props, 168 | prevState: State 169 | ) { 170 | if (prevProps.list.length < this.props.list.length) { 171 | this.listScrollHeight = this.listRef.value.scrollHeight; 172 | } 173 | } 174 | 175 | componentDidUpdate(prevProps: Props, prevState: State) { 176 | if (this.listScrollHeight !== null) { 177 | this.listRef.value.scrollTop += 178 | this.listRef.value.scrollHeight - snapshot; 179 | this.listScrollHeight = null; 180 | } 181 | } 182 | 183 | // ... 184 | } 185 | ``` 186 | 187 | Ultimately, the team voted against this approach because it encourages mutations and may invite other side-effects in a lifecycle that is intended to be used for a very specific purpose. 188 | 189 | # Adoption strategy 190 | 191 | Since this lifecycle- and async rendering in general- is new functionality, adoption will be organic. Documentation and dev-mode warnings have already been created to encourage people to move away from render phase lifecycles like `componentWillUpdate` in favor of commit phase lifecycles. 192 | 193 | # How we teach this 194 | 195 | Lifecycle documentation on the website. Add a before and after example (like [the one above](#basic-example)) to the [Update on Async Rendering](https://github.com/reactjs/reactjs.org/pull/596) blog post "recipes". 196 | 197 | # Unresolved questions 198 | 199 | None presently. 200 | -------------------------------------------------------------------------------- /text/0051-profiler.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2018-05-22 2 | - RFC PR: https://github.com/reactjs/rfcs/pull/51 3 | - React Issue: (leave this empty) 4 | 5 | # Summary 6 | 7 | New React profiling component that collects timing information in order to measure the "cost" of rendering. 8 | 9 | This component will also integrate with the [experimental `interaction-tracking` API](https://github.com/facebook/react/pull/13234) so that tracked interactions can be correlated with the render(s) they cause. This enables the calculation of "wall time" (elapsed real time) from when e.g. a user clicks a form button until when the DOM is updated in response. It also enables long-running renders to be more easily attributed and reproduced. 10 | 11 | Note that an experimental release of the `Profiler` component was first included with version 16.4 as `React.unstable_Profiler`. It did not yet support interaction tracing as that package had not been released. 12 | 13 | # Usage example 14 | 15 | `Profiler` can be declared anywhere within a React tree to measure the cost of rendering that portion of the tree. 16 | 17 | For example, to profile a `Navigation` component and its descendants: 18 | ```js 19 | render( 20 | 21 | 22 | 23 | 24 |
25 | 26 | ); 27 | ``` 28 | 29 | Multiple `Profiler` components can be used to measure different parts of an application: 30 | ```js 31 | render( 32 | 33 | 34 | 35 | 36 | 37 |
38 | 39 | 40 | ); 41 | ``` 42 | 43 | `Profiler` components can also be nested to measure different components within the same subtree: 44 | ```js 45 | render( 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | ); 59 | ``` 60 | 61 | Although `Profiler` is a light-weight component, it should be used only when necessary; each use adds some CPU and memory overhead to an application. 62 | 63 | # Motivation 64 | 65 | It is important for render timing metrics to work properly with React's experimental concurrent rendering mode. In concurrent mode, React may yield to the browser periodically so that an app remains responsive even on low-powered devices. This yielded time (when React is not running) should not be included when considering the "cost" of a render. This distinction is not possible to implement in user space. 66 | 67 | Timing measurements should also be significantly lighter weight than the current User Timing API so that they can be gathered in production without negatively impacting user experience. (The User Timing API is currently disabled for production because it is slow.) In addition to a faster implementation, we can further limit the impact on existing apps by creating a new production + profiling bundle. This way, apps that don't make use of the `Profiler` component (or wish to disable it globally) will not incur any additional overhead. (The `Profiler` component will render its children in production mode but its `onRender` callback will not be called.) 68 | 69 | # Detailed design 70 | 71 | The `onRender` callback is called each time a component within the `Profiler` renders. It receives the following parameters: 72 | ```js 73 | function onRenderCallback( 74 | id: string, 75 | phase: "mount" | "update", 76 | actualDuration: number, 77 | baseDuration: number, 78 | startTime: number, 79 | commitTime: number, 80 | interactions: Set<{ name: string, timestamp: number }>, 81 | ): void { 82 | // Aggregate or log render timings... 83 | } 84 | 85 | ``` 86 | 87 | #### `id: string` 88 | The `id` value of the `Profiler` tag that was measured. This value can change between renders if e.g. it is derived from `state` or `props`. 89 | 90 | #### `phase: "mount" | "update"` 91 | Identifies whether this component has just been mounted or re-rendered due to a change in `state` or `props`. 92 | 93 | #### `actualDuration: number` 94 | Time spent rendering the `Profiler` and its descendants for the current (most recent recent) update. This time tells us how well the subtree makes use of memoization (e.g. `React.memo`, `useMemo`, `shouldComponentUpdate`). 95 | 96 | Ideally, this time should decrease significantly after the initial mount as many of the descendants will only need to re-render if their specific `props` change. 97 | 98 | Note that in async mode, under certain conditions, React might render the same component more than once as part of a single commit. (In this event, the "actual" time for an update might be larger than the initial time.) 99 | 100 | #### `baseDuration: number` 101 | Duration of the most recent `render` time for each individual component within the `Profiler` tree. In other words, this value will only change when a component is re-rendered. It reflects a worst-case cost of rendering (e.g. the initial mount or a tree with no memoization). 102 | 103 | #### `startTime: number` 104 | Start time identifies when a particular commit started rendering. Although insufficient to determine the cause of the render, it can at least be used to rule out certain interactions (e.g. mouse click, Flux action). This may be helpful if you are also collecting other types of interactions and trying to correlate them with renders. 105 | 106 | Start time isn't just the commit time less the "actual" time, because in async rendering mode React may yield during a render. This "yielded time" (when React was not doing work) is not included in either the "actual" or "base" time measurements. 107 | 108 | #### `commitTime: number` 109 | Commit time could be roughly determined using e.g. `performance.now()` within the `onRender` callback, but multiple `Profiler` components would end up with slightly different times for a single commit. Instead, an explicit time is provided (shared between all `Profiler`s in the commit) enabling them to be grouped if desirable. 110 | 111 | #### `interactions: Set<{ name: string, timestamp: number }>` 112 | Set of interactions that were being tracked (via the `interaction-tracking` package) when this commit was initially scheduled (e.g. when `render` or `setState` were called). 113 | 114 | In the event of a cascading render (e.g. an update scheduled from an effect or `componentDidMount`/`componentDidUpdate`) React will forward these interactions along to the subsequent `onRender` calls. 115 | 116 | # Drawbacks 117 | 118 | Overuse of this component might negatively impact application performance. 119 | 120 | # Alternatives 121 | 122 | None considered. 123 | 124 | # Adoption strategy 125 | 126 | This is an entirely new component. Adoption can be organic and gradual. 127 | 128 | # How we teach this 129 | 130 | A reactjs.org blog post would be a good initial start. 131 | 132 | Perhaps we could provide some sort of discoverability within React DevTools. 133 | 134 | # Related proposals 135 | 136 | * [facebook/react/pull/13253](https://github.com/facebook/react/pull/13253): Integration with the proposed `interaction-tracking` package 137 | * [facebook/react-devtools/pull/1069](https://github.com/facebook/react-devtools/pull/1069): Integration with React DevTools 138 | 139 | # Related documentation 140 | * [fb.me/react-profiling](http://fb.me/react-profiling): Instructions for profiling in production 141 | * [fb.me/react-interaction-tracing](http://fb.me/react-interaction-tracing): Instructions for using interaction tracing with React -------------------------------------------------------------------------------- /text/0063-memo.md: -------------------------------------------------------------------------------- 1 | 2 | - Start Date: 2018-10-18 3 | - RFC PR: https://github.com/reactjs/rfcs/pull/63 4 | - React Issue: https://github.com/facebook/react/pull/13748 5 | 6 | # Summary 7 | 8 | `React.memo` lets you [memoize](https://en.wikipedia.org/wiki/Memoization) the render output from any component (e.g. a function component), and bail out of unnecessary updates. It is an optimization, similar to how you'd use `React.PureComponent` if you were writing a class. 9 | 10 | # Basic example 11 | 12 | ```js 13 | import { memo } from 'react'; 14 | 15 | function Button(props) { 16 | // Component code 17 | } 18 | 19 | export default memo(Button); 20 | ``` 21 | 22 | Wrapping a component into `React.memo` makes it bail out of rendering when the props are shallowly equal. 23 | 24 | You can also pass an `arePropsEqual(prevProps, nextProps)` function as a second argument to customize the bailout condition: 25 | 26 | ```js 27 | function arePropsEqual(prevProps, nextProps) { 28 | return prevProps.color.id === nextProps.color.id; 29 | } 30 | 31 | export default memo(Button, arePropsEqual); 32 | ``` 33 | 34 | # Motivation 35 | 36 | Today in React, function components and `PureComponent` provide two different kinds of optimizations that can't be unified. 37 | 38 | Function components let us avoid constructing a class instance. This benefits the initial render. Function components also tend to minify better than classes, which helps reduce the bundle size. 39 | 40 | On the other hand, in order to optimize updates we sometimes want to bail out of rendering by **[memoizing](https://en.wikipedia.org/wiki/Memoization)** the rendered result. To do that today, you have to convert a function component to a `PureComponent` class (or a class with custom `shouldComponentUpdate`). This is true even if you don't use features like state and other lifecycle methods. So it makes the initial render time a bit worse, but updates are potentially faster. 41 | 42 | By having `memo` as a first-class API in React itself, we can remove the need to make a choice between these optimizations. It makes it easy to memoize the output of function components without introducing other extra costs. 43 | 44 | This also helps address a common argument in teams that can't decide whether to use one or the other optimization, by letting you use them together. And unlike a userland `memo()` higher-order component implementation, the one built into React can be more efficient by avoiding an extra component layer. 45 | 46 | 47 | # Detailed design 48 | 49 | `React.memo` returns a component type that tells React "render the inner type, but bail out on updates if props are shallowly equal". In other words, the rendering result is memoized. The prop comparison function can be specified as a second argument to `React.memo`. 50 | 51 | `React.memo` accepts any valid component type as the first argument. This ensures that it can safely wrap an import without being concerned about its implementation details. 52 | 53 | `React.memo` returns a special component type, similar to `React.forwardRef`. Returning an actual function or a class instead would defeat the optimization as it would create an additional layer and couldn't be faster than just `React.PureComponent`. 54 | 55 | The second argument is `arePropsEqual` (rather than, say, `arePropsDifferent`) so that you can more easily pass a different off-the-shelf implementation (which tend to be written in the positive form). 56 | 57 | # Drawbacks 58 | 59 | - It's doable in user space (with worse performance). 60 | - There is some duplication with `PureComponent` API. 61 | - There may be confusion over whether the second argument is `arePropsEqual` or `arePropsDifferent`. 62 | - This concept was previously known as "pure component" even though it didn't match the definition of purity. 63 | 64 | # Alternatives 65 | 66 | - Only allow classes to have bailouts (status quo). 67 | - Function components could have `memo` by default (breaking change and potentially slow). 68 | - Instead of a wrapper, put a flag on the component instead. 69 | - Give it a long name instead of `memo`. 70 | - Call it something different like `pure`. 71 | - Don't allow to specify the custom equality function. 72 | - `memo` could be an actual higher-order component rather than return a special type. 73 | - `memo` could accept `arePropsDifferent` as a second argument instead. 74 | - `memo` could be constrained to only work with function components. 75 | 76 | # Adoption strategy 77 | 78 | This is not a breaking change. You can start using function components in more places in case you previously felt limited by lack of a `PureComponent` alternative. You don't have to use this. 79 | 80 | # How we teach this 81 | 82 | We're intentionally breaking away from the existing naming of `PureRenderMixin` and `PureComponent` by calling the wrapper `memo`. This is because conceptually this is *memoization* and is unrelated to the function purity. 83 | 84 | We can now decouple teaching _when to use_ this optimization from _how to apply_ it, and applying it no longer forces you to rewrite a component as a class. 85 | -------------------------------------------------------------------------------- /text/0064-lazy.md: -------------------------------------------------------------------------------- 1 | 2 | Start Date: 2018-10-19 3 | - RFC PR: https://github.com/reactjs/rfcs/pull/64 4 | - React Issue: https://github.com/facebook/react/pull/13885 5 | 6 | # Summary 7 | 8 | `React.lazy` adds first-class support for code splitting components to React. It takes a module object and returns a special component type. 9 | 10 | >Note 11 | > 12 | >This RFC is intentionally scoped to supporting default imports. We may in the future submit another RFC concerning named imports. In the meantime you can also implement support for named imports in userland with a lower-level Suspense API (to be discussed in another RFC). 13 | 14 | # Basic example 15 | 16 | ```js 17 | import Input from './Input'; // Regular import 18 | const Button = React.lazy(() => import('./Button')); // Dynamic import 19 | 20 | function Dialog() { 21 | // You can mix normal and lazy-loaded components in one tree 22 | return ( 23 |
24 | 25 |
34 |
35 | ); 36 | } 37 | ``` 38 | 39 | This rest of this section is covered by: 40 | 41 | * **[Hooks at a Glance](https://reactjs.org/docs/hooks-overview.html)** 42 | 43 | # Motivation 44 | 45 | This section is covered by: 46 | 47 | * **[Introducing Hooks: Motivation](https://reactjs.org/docs/hooks-intro.html#motivation)** 48 | 49 | # Detailed design 50 | 51 | This section is covered by: 52 | 53 | * **[Using the State Hook](https://reactjs.org/docs/hooks-state.html)** 54 | * **[Using the Effect Hook](https://reactjs.org/docs/hooks-effect.html)** 55 | * **[Rules of Hooks](https://reactjs.org/docs/hooks-rules.html)** 56 | * **[Writing Custom Hooks](https://reactjs.org/docs/hooks-custom.html)** 57 | * **[Hooks API Reference](https://reactjs.org/docs/hooks-reference.html)** 58 | * **[Hooks FAQ](https://reactjs.org/docs/hooks-faq.html)** 59 | 60 | Some subtle aspects of this design that we especially appreciate: 61 | 62 | * Values returned from one Hook can be passed to another. For example, an effect can read the return value from `useState` because it is automatically in function scope. 63 | * Custom Hooks allow abstracting logic without any special involvement from React. 64 | * One variable per `useState` call instead of a single `this.state` object means fewer object property accesses that are hard for VMs to optimize. 65 | * State variables names (with `useState`) can be automatically minified. 66 | * No changes to build tooling are required. 67 | 68 | # Drawbacks 69 | 70 | A non-exhaustive list of drawbacks of this Hooks design follows. 71 | 72 | * Introducing a new way to write components means more to learn and means confusion while both classes and functions are used. 73 | * The “Rules of Hooks”: in order to make Hooks work, React requires that Hooks are called unconditionally. Component authors may find it unintuitive that Hook calls can't be moved inside `if` statements, loops, and helper functions. 74 | * The “Rules of Hooks” can make some types of refactoring more difficult. For example, adding an early return to a component is no longer possible without moving all Hook calls to before that conditional. 75 | * Event handlers need to be recreated on each render in order to reference the latest copy of props and state, which reduces the effectiveness of `PureComponent` and `React.memo`. 76 | * It's possible for closures (like the ones passed to `useEffect` and `useCallback`) to capture **old versions** of props and state values. In particular, this happens if the “inputs” array is inadvertently missing one of captured variables. This can be confusing. 77 | * React relies on internal global state in order to determine which component is currently rendering when each Hook is called. This is “less pure” and may be unintuitive. 78 | * `React.memo` (as a replacement for `shouldComponentUpdate`) only has access to the old and new props; there's no easy way to skip rerendering for an inconsequential state update. 79 | * `useState` uses a tuple return value that requires typing the same name twice to declare a state field (like `const [rhinoceros, setRhinoceros] = useState(null);`), which may be cumbersome for long names. 80 | * `useState` uses the overloaded type `() => T | T` to support lazy initialization. But when storing a function in state (that is, when `T` is a function type) you must always use a lazy initializer `useState(() => myFunction)` because the types are indistinguishable at runtime. Similarly, the functional updater form must be used when setting state to a new function value. 81 | 82 | # Alternatives 83 | 84 | Possible alternatives follow. In our opinion, none of these cleanly solve all the problems that Hooks do. 85 | 86 | * The status quo: supporting state and effects only in class components, with no changes. 87 | * Alternative designs that allow using state and effects without classes: 88 | * Language-level syntax akin to `state foo = 5;` in the style of [DisplayScript](http://displayscript.org/). 89 | * Built-in render prop components that provide stateful features, like [Reactions Component](https://github.com/reactions/component). 90 | * Built-in higher-order components [that pass stateful features as arguments](https://mobile.twitter.com/acdlite/status/971598256454098944). 91 | * Other “stateful function components” ideas laid out in [react-future](https://github.com/reactjs/react-future). 92 | * Alternative designs that facilitate sharing of stateful logic: 93 | * Syntax sugar for render props, like [an `adopt` keyword](https://gist.github.com/trueadm/17beb64288e30192f3aa29cad0218067) or [Epitath](https://medium.com/astrocoders/epitath-in-memoriam-render-props-and-hocs-9f76dd911f9e). 94 | * Reintroducing [mixins in class components](https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html). 95 | 96 | # Adoption strategy 97 | 98 | We're planning to recommend a [gradual adoption strategy](https://reactjs.org/docs/hooks-intro.html#gradual-adoption-strategy) for Hooks. Hooks don't break existing patterns, and they can be used side by side with class components. It's possible that codemod could be written to automatically convert simple class components to use Hooks instead, but none is currently planned. 99 | 100 | # How we teach this 101 | 102 | The documentation linked above is our best attempt at teaching this. Over time we expect that we'll integrate the Hooks documentation with the main concept docs instead of having it in a separate section. 103 | 104 | # Credits and prior art 105 | 106 | Hooks synthesize ideas from several different sources: 107 | 108 | * Our old experiments with functional APIs in the [react-future](https://github.com/reactjs/react-future/tree/master/07%20-%20Returning%20State) repository. 109 | * React community's experiments with render prop APIs, including [Ryan Florence](https://github.com/ryanflorence)'s [Reactions Component](https://github.com/reactions/component). 110 | * [Dominic Gannaway](https://github.com/trueadm)'s [`adopt` keyword](https://gist.github.com/trueadm/17beb64288e30192f3aa29cad0218067) proposal as a sugar syntax for render props. 111 | * State variables and state cells in [DisplayScript](http://displayscript.org/introduction.html). 112 | * [Reducer components](https://reasonml.github.io/reason-react/docs/en/state-actions-reducer.html) in ReasonReact. 113 | * [Subscriptions](http://reactivex.io/rxjs/class/es6/Subscription.js~Subscription.html) in Rx. 114 | * [Algebraic effects](https://github.com/ocamllabs/ocaml-effects-tutorial#2-effectful-computations-in-a-pure-setting) in Multicore OCaml. 115 | 116 | [Sebastian Markbåge](https://github.com/sebmarkbage) came up with the original design for Hooks, later refined by [Andrew Clark](https://github.com/acdlite), [Sophie Alpert](https://github.com/sophiebits), [Dominic Gannaway](https://github.com/trueadm), and other members of the React team. 117 | -------------------------------------------------------------------------------- /text/0118-lazy-context-propagation.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2019-06-19 2 | - RFC PR: https://github.com/reactjs/rfcs/pull/118 3 | - React Issue: https://github.com/facebook/react/pull/20890 4 | 5 | # Summary 6 | 7 | This RFC describes a new lazy context propagation implementation which uses the work React is already going to do when possible, before resorting to explicit context dependency searches. This results in improved performance characteristics for some use cases. Additionally it may enable new kinds of context update bailouts to be implemented. 8 | 9 | It does not modify the public API of React in any way. 10 | 11 | # Motivation 12 | 13 | The eager context propagation in place today is executed per ContextProvider. When a ContextProvider is updating to a new value every constituent fiber has its context dependencies checked against the updating Context and work is scheduled if necessary. 14 | 15 | This approach has two direct behaviors that can be optimized. First, for any given render, each ContextProvider with a new value will result in the constituent sub-tree for that ContextProvider to be visited. In a simplified scenario, with ContextProviders at the top of the tree, if `N` ContextProviders are updating then the fiber tree will be traversed during propagation exactly `N` times. 16 | 17 | The second optimization opportunity is that, during an update, portions of the tree may be unmounted. The eager propagation visits these soon-to-be-unmounted sub-trees since it can’t know yet they are going to be removed causing wasted work walking fibers and checking context dependencies. 18 | 19 | The lazy propagation described in this RFC delays doing any context dependency checking and look-ahead propagation except when React would otherwise bail out of work on this fiber or its child fibers. It also checks all contexts simultaneously. These two changes allow for propagation tree traversal to be somewhere between 0 and 1 times (only parts of the tree will be visited during propagation and only once regardless of the number of contexts with changed bits). In addition propagation will only happen when no more scheduled work is found for a given sub-tree. Since unmounting is the result of changes during render, unmounts will now happen before context propagation begins. 20 | 21 | In addition to direct optimizations, the lazy propagation will allow for some interesting new APIs. For instance a new invariant exists where if propagation has begun we know the child tree would otherwise have bailed out of updates (the workInProgress sub-tree would be the current sub-tree due to structural sharing). In this environment we can do more expensive and dynamic checking of context dependencies without fear that other work will invalidate these computations. In particular, rather than having a statically determined update bailout based on `observedBits` it would be possible to do a dynamic check running an arbitrary function receiving the context value to determine if a bailout should happen. It can be dynamic here because we know the result will not be invalidated by other work because know no other work has been scheduled. This idea will be explored more in a separate RFC soon. 22 | 23 | 24 | # Detailed design 25 | 26 | Imagine React did not have any kind of bailout system in place during beginWork. When context values change there would be no need to propagate them because the entire tree would re-render and each context reading component would read the latest value afresh. 27 | 28 | When reintroducing the idea of bailing out of work we would now have a problem since some work would be skipped. This proposal essentially boils down to avoiding bailouts in beginWork if the fiber under consideration depends on a changed context value. 29 | 30 | ## Changes to work bailouts (ReactFiberBeginWork) 31 | 32 | There are actually 3 distinct bailout classes that need be handled 33 | 34 | ### Work begins on fiber; no update scheduled 35 | 36 | Before calling an update*Component function in beginWork, if new and old props are equal and updateExpirationTime < renderExpirationTime the update step is skipped. To avoid this bailout when context dependencies have changed we need to check them before entering this branch and reset the updateExpirationTime if necessary. 37 | 38 | ### Work begins on fiber; fiber type has built-in bailouts 39 | 40 | Memo Components and Class Components with shouldComponentUpdate offer bailout mechanisms that are executed during the update step of a component. Even if workInProgress props are different from current props the fiber may bail out of work. In these cases, before choosing to bail out of work, context dependencies should be evaluated and if changes are observed the bailout should be avoided. 41 | 42 | ### Work is bailing out on fiber; child fibers will be skipped 43 | 44 | During a bailout of a fiber, this fiber’s childExpirationTime is checked to see if any descendents have work scheduled and if not, work skips over child fibers (reusing the current fibers from the previously committed tree). When this is about to happen a tree-walking propagation algorithm will run, nearly identical to the existing propagation algorithm ensuring we do not miss any necessary context updates. 45 | 46 | 47 | ### Summary of changes 48 | - In `bailoutOnAlreadyFinishedWork`, implement an invariant that guarantees context dependencies were checked prior to initiation. It will be a bug to bail out of work without some explicit context dependency check or where it is known the fiber type cannot have context dependencies 49 | - Implement a `canBailout` function which does a check (if necessary) and only returns true if it is safe to bail out of work on this fiber. Calling this would satisfy the invariant above 50 | - Checks context dependencies if needed 51 | - Can be called multiple times (bailouts can happen at more than one point through the steps of beginWork), but will only check dependencies the first time 52 | - During `bailoutOnAlreadyFinishedWork`, run `propagateContexts` before checking childExpirationTime to determine if work should continue deeper or return to this fiber’s sibling 53 | 54 | ## Preventing more than one check per fiber 55 | 56 | The context propagation when it does run will cause context dependencies to be checked on fibers that have not yet had work begun. If new updates are scheduled work will then begin on these fibers. To avoid rechecking context dependencies a second time we can add a propagationSigil to every Fiber. Whenever a Provider is pushed (beginWork for a ContextProvider component) we will create a new module global `propagationSigil` (an empty object) which we can assign to fibers that have had their context dependencies checked. Later if another check starts and the propagationSigil is the same the dependency check can be skipped. When a new Provider is pushed the sigil will change ensuring we re-run checks given the new aggregate context state has been modified. 57 | 58 | ## Early Propagation Bailouts 59 | 60 | If we know there are no contexts with changedBits we can skip all propagation. a new module global `propagationHasChangedBits` will be set as Providers are pushed and popped. it will be false when we know there are no Providers with changedBits 61 | 62 | ## Changes to ContextProviders 63 | 64 | - Stop triggering a propagation on beginWork 65 | - During pushProvider 66 | - set a `_currentChangedBits` value 67 | - create and set a new `propagationSigil` 68 | - declare whether any pushed contexts have changed bits in via `propagationHasChangedBits` boolean. 69 | - During popProvider 70 | - Restore previous `_currentChangedBits` value 71 | - Restore previous `propagationSigil` 72 | - Restore previous `propagationHasChangedBits` 73 | 74 | ## Changes to Propagation Algorithm 75 | 76 | - Break out context dependency checking into a separate method that can be called from beginWork without having to run the full propagation algorithm. It will read `_currentChangedBits` from each context instead of taking `changedBits` as an argument 77 | - Bail out of propagation if no contexts have changed bits (see `propagationHasChangedBits`) 78 | - Will consider any context dependency that has intersecting changed bits as requiring an update (checking all contexts together) 79 | - Does not go deeper on fibers with `expirationTime | childExpirationTime >= renderExpirationTime`. These will be visited during beginWork and do not require any kind of look-ahead context propagation. 80 | - Schedules work on any ContextProviders it visits. This is necessary because this Provider might mask a currently changed value. We can no longer propagate *through* any Providers 81 | - Set current `propagationSigil` on fiber to avoid rechecking during `beginWork` 82 | 83 | 84 | # Drawbacks 85 | 86 | - This implementation is more complex & the performance benefits are not yet quantified. 87 | - Adding new bailouts would now be more complicated because context needs to be considered 88 | 89 | 90 | # Alternatives 91 | 92 | Keep existing propagation algorithm 93 | 94 | # Adoption strategy 95 | 96 | Adoption will be automatic, there are no public api changes. 97 | 98 | # How we teach this 99 | 100 | No teaching necessary 101 | 102 | # Unresolved questions 103 | 104 | Suspense will need special handling (in particular server side rendered suspense components). I do not understand this feature well enough yet to know how to properly integrate it with Lazy context propagation but I believe it can be done. 105 | -------------------------------------------------------------------------------- /text/0139-profiler-measure-commit-durations.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2020-03-05 2 | - RFC PR: https://github.com/reactjs/rfcs/pull/139 3 | - React Issue: https://github.com/facebook/react/pull/17910 4 | 5 | # Summary 6 | 7 | This proposal adds two new callback props to `` components: `onCommit` and `onPostCommit`. These callbacks will receive information about how much time was spent in the most recent commit for layout effects (`useLayoutEffect` as well as `componentDidMount`, `componentDidUpdate`, and `componentWillUnmount`) and passive effects (`useEffect`). 8 | 9 | # Basic example 10 | 11 | ```jsx 12 | 18 | {/* Components being profiled */} 19 | 20 | ``` 21 | 22 | # Motivation 23 | 24 | React's [Profiler API](https://reactjs.org/docs/profiler.html) currently only measures performance of components during the ["render phase"](https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects). Render phase performance is important, but "commit phase" performance is arguably even more important since it can't be time sliced and blocks the browser from painting or executing other JavaScript. 25 | 26 | 27 | # Detailed design 28 | 29 | ### What new fields are required? 30 | * Add a `stateNode` field to Profiler Fibers with `effectDuration` and `passiveEffectDuration` attributes. 31 | 32 | ### How do we measure and track the new durations? 33 | At a high level: 34 | * React will reset effect durations for each Profiler [during the render phase](https://github.com/facebook/react/blob/64aae7b06fa47e126b0ba9c0ba9896caa803528e/packages/react-reconciler/src/ReactFiberBeginWork.js#L573-L590). 35 | * This provides DevTools a chance to read layout effect durations from its commit hook. 36 | * A new DevTools hook will need to be added for passive effects though, as those are usually not measured before the commit hook is called (except for unmount cases). 37 | * React will measure time spent in `use*Effect` and `componentDid*` methods by calling `performance.now()` (or `Date.now()` if the Performance API is unavailable) before and after executing user code. 38 | * Each measurement will be added to the nearest Profiler `stateNode`. 39 | * To find the nearest Profiler Fiber, React will walk the `return` tree. 40 | * Each Profiler will later propagate the durations up to its next nearest Profiler ancestor [after calling user callbacks](https://github.com/facebook/react/blob/64aae7b06fa47e126b0ba9c0ba9896caa803528e/packages/react-reconciler/src/ReactFiberCommitWork.js#L594-L621). 41 | 42 | #### Layout effect durations 43 | Measuring layout effects and calling `onCommit` is fairly straight forward: 44 | * During the commit phase: 45 | * React will use the Profiler timer to measure the duration of layout effects (and class `componentDid*` lifecycle methods). 46 | * React will call Profiler `onCommit` props with accumulated durations. 47 | * React will then bubble durations to the next nearest Profiler. 48 | 49 | #### Passive effect durations 50 | Measuring and reporting passive effects is more complex than layout effects. In particular, deciding on *when* to call `onPostCommit` is tricky because: 51 | 1. Passive effects won't have been processed by the time we are committing the tree (at least not for the render work we're currently committing). Duration values will represent the previous round of passive effects. 52 | 1. We *could* call the previous `onPostCommit` callback at this time, although that presents additional problems since memoized props and interactions may have changed since then: 53 | 1. The `onPostCommit` callback may have changed in the most recent render. 54 | 1. The `memoizedInteractions` set corresponds to the current batch of render work, so it would be incorrect. 55 | 1. The `id` of the Profiler also corresponds to the current batch of render work. (It may have changed since the render our passive effects are part of.) 56 | 1. This would make resetting the passive effect durations a little more complicated as well. I think we should do this during the begin phase, so DevTools has a chance to read these values, but if we did that we'd erase the previous passive effect durations too. 57 | 58 | Measuring passive effects and calling `onPostCommit` is more complex then: 59 | * React will use the Profiler timer to measure the duration of passive effects while flushing in preparation for a new render phase. 60 | * Once all passive effects have been flushed, React will bubble any accumulated durations up through ancestor Profilers and call any registered `onPostCommit` callbacks. 61 | * This must be done before calling `flushSyncCallbackQueue()` in case the flushed effects schedule more work. 62 | 63 | In order to allow the DevTools Profiler to record these durations we will also need to add a new DevTools hook. (Normally DevTools reads values in its commit hook, but this won't work for passive effect durations for reasons mentioned above.) 64 | 65 | #### Before mutation durations 66 | 67 | Currently this category only includes the class component lifecycle `getSnapshotBeforeUpdate`, but (we will probably add a before-mutation hook at some point too). 68 | 69 | Given the currently proposed callbacks (`onRender`, `onCommit`, `onPostCommit`) I believe it's best to leave before mutation durations for a future RFC (e.g. `onPreCommit`). Pre-mutation effects aren't very common, so this seems less urgent. 70 | 71 | #### Effect cleanup functions 72 | Measuring cleaning functions also presents a few options, (since cleanup functions don't get run until an update or unmount- after we've already reported durations). 73 | 74 | There are a couple of options: 75 | 1. ☆☆☆ Delay reporting commit durations until the *next* commit (after cleanup functions have been run) so the durations are inclusive of both. This has similar complexity downsides as mentioned above for passive effects though. 76 | 1. ★☆☆ Add new callbacks for cleanup durations (e.g. `onCommitCleanup`? `onCommitDestroy`?) and report them separately. This would provide the most insight into where slowness comes from, but I also think this might be overkill. 77 | 1. ★★☆ Don't measure these durations. Typically cleanup functions are small (e.g. removing an event listener). This would be a continued blindspot in the Profiler API though. 78 | 1. ★★★ Measure cleanup functions, but include their durations as part of the current commit. An argument could be made for this being the *best* way to think of them, since their durations impact the *current* commit (not the previous one), although it might be confusing since e.g. interactions are for the current commit. 79 | 80 | For this RFC, I am proposing the 4th option above. 81 | 82 | ### What info do the new callbacks receive? 83 | The new callbacks will receive the following parameters: 84 | * **`id: string`** - The id prop of the Profiler tree that has just committed. This can be used to identify which part of the tree was committed if you are using multiple profilers. 85 | * **`phase: "mount" | "update"`** - Identifies whether the tree has just been mounted for the first time or re-rendered due to a change in props, state, or hooks. 86 | * **`duration: number`** - Total duration of all user effects (or class lifecycle methods) for the most recent commit phase. 87 | * **`commitTime: number`** - Start time of the current commit (shared between all Profilers in the commit, enabling them to be grouped if desirable). 88 | * **`interactions`**: Set - Set of “interactions” that were being traced the update was scheduled (e.g. when render or setState were called). 89 | 90 | # Drawbacks 91 | 92 | The main drawback to doing this is that Profiling adds memory and CPU overhead. I think the design of this API will limit the impact of both though. 93 | 94 | ### Memory 95 | Memory impact should be minimal since it only impacts Profiler Fibers. (There should be no impact at all for the production bundle or for apps that don't use the Profiler API.) 96 | 97 | ### CPU/time: 98 | Recording effect durations and finding the nearest Profiler Fiber will add some additional CPU overhead to the commit phase, but it should hopefully be minimal: 99 | * `performance.now()` calls are fast and there should not be many of these in the common case. 100 | * Traversing the tree to find the nearest Profiler fiber should typically be a logarithmic operation. 101 | 102 | The above will also only impact the commit phase for portions of the tree that are in Profile mode. Apps that don't use the Profiler API will not incur the overhead. 103 | 104 | # Alternatives 105 | 106 | * Rather than `onCommit` and `onPostCommit` we could name the new callback props `onLayoutEffects` and `onEffects`. 107 | * An alternative to walking the tree to find the nearest Profiler ancestor could be to store a pointer on each Fiber to the nearest Profiler Fiber. This could save some tree traversal time, but at the expense of adding a new field to every Fiber which would negatively impact memory. 108 | 109 | # Adoption strategy 110 | 111 | Both new `Profiler` callback props will be optional. Existing callsites will continue to report render phase duration and can be gradually upgraded in place to also report commit phase duration as desired. 112 | 113 | # How we teach this 114 | 115 | I will write a React block post (or similar) with examples of how to use the new APIs and what they mean. 116 | 117 | # Unresolved questions 118 | 119 | * It would be very difficult to report time spent in cleanup functions for passive effects in certain umount cases (with nested Profilers) given the current unmount implementation. We plan to refactor this code as part of the concurrent mode release. The question here is: is it okay to land an initial version of this API that does not handle this case (and fix it as part of the refactor)? 120 | * Should we add interaction tracing support to these new methods? Tracing already adds a lot of complexity and (arguably) little value. -------------------------------------------------------------------------------- /text/0147-use-mutable-source.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2020-02-13 2 | - RFC PR: [18000](https://github.com/facebook/react/pull/18000) 3 | - React Issue: N/A 4 | 5 | # useMutableSource 6 | 7 | `useMutableSource()` enables React components to **safely** and **efficiently** read from a mutable external source in Concurrent Mode. The API will detect mutations that occur during a render to avoid tearing and it will automatically schedule updates when the source is mutated. 8 | 9 | # Basic example 10 | 11 | This hook is designed to support a variety of mutable sources. Below are a few example cases. 12 | 13 | ### Browser APIs 14 | 15 | `useMutableSource()` can also read from non traditional sources, e.g. the shared Location object, so long as they can be subscribed to and have a "version". 16 | 17 | ```js 18 | // May be created in module scope, like context: 19 | const locationSource = createMutableSource( 20 | window, 21 | // Although not the typical "version", the href attribute is stable, 22 | // and will change whenever part of the Location changes, 23 | // so it's safe to use as a version. 24 | () => window.location.href 25 | ); 26 | 27 | // Because this method doesn't require access to props, 28 | // it can be declared in module scope to be shared between components. 29 | const getSnapshot = window => window.location.pathname; 30 | 31 | // This method can subscribe to root level change events, 32 | // or more snapshot-specific events. 33 | // 34 | // Because this method doesn't require access to props, 35 | // it can be declared in module scope to be shared between components. 36 | const subscribe = (window, callback) => { 37 | window.addEventListener("popstate", callback); 38 | return () => window.removeEventListener("popstate", callback); 39 | }; 40 | 41 | function Example() { 42 | const pathName = useMutableSource(locationSource, getSnapshot, subscribe); 43 | 44 | // ... 45 | } 46 | ``` 47 | 48 | ### Selectors that use props 49 | 50 | Sometimes a state value is derived using component `props`. In this case, `useCallback` should be used to keep the snapshot and subscribe functions stable. 51 | 52 | ```js 53 | // May be created in module scope, like context: 54 | const userDataSource = createMutableSource(userData, () => userData.version); 55 | 56 | // This method can subscribe to root level change events, 57 | // or more snapshot-specific events. 58 | // In this case, since Example is only reading the "friends" value, 59 | // we only have to subscribe to a change in that value 60 | // (e.g. a "friends" event) 61 | // 62 | // Because this method doesn't require access to props, 63 | // it can be declared in module scope to be shared between components. 64 | const subscribe = (userData, callback) => { 65 | userData.addEventListener("friends", callback); 66 | return () => userData.removeEventListener("friends", callback); 67 | }; 68 | 69 | function Example({ onlyShowFamily }) { 70 | // Because the snapshot depends on props, it has to be created inline. 71 | // useCallback() memoizes the function though, 72 | // which lets useMutableSource() know when it's safe to reuse a snapshot value. 73 | const getSnapshot = useCallback( 74 | userData => 75 | userData.friends 76 | .filter( 77 | friend => !onlyShowFamily || friend.relationshipType === "family" 78 | ) 79 | .map(friend => friend.id), 80 | [onlyShowFamily] 81 | ); 82 | 83 | const friendIDs = useMutableSource(userDataSource, getSnapshot, subscribe); 84 | 85 | // ... 86 | } 87 | ``` 88 | 89 | ### Redux stores 90 | 91 | Redux users would likely never use the `useMutableSource` hook directly. They would use a hook provided by Redux that uses `useMutableSource` internally. 92 | 93 | ##### Mock Redux implementation 94 | ```js 95 | // Somewhere, the Redux store needs to be wrapped in a mutable source object... 96 | const mutableSource = createMutableSource( 97 | reduxStore, 98 | // Because the state is immutable, it can be used as the "version". 99 | () => reduxStore.getState() 100 | ); 101 | 102 | // It would probably be shared via the Context API... 103 | const MutableSourceContext = createContext(mutableSource); 104 | 105 | // Because this method doesn't require access to props, 106 | // it can be declared in module scope to be shared between hooks. 107 | const subscribe = (store, callback) => store.subscribe(callback); 108 | 109 | // Oversimplified example of how Redux could use the mutable source hook: 110 | function useSelector(selector) { 111 | const mutableSource = useContext(MutableSourceContext); 112 | 113 | const getSnapshot = useCallback(store => selector(store.getState()), [ 114 | selector 115 | ]); 116 | 117 | return useMutableSource(mutableSource, getSnapshot, subscribe); 118 | } 119 | ``` 120 | 121 | #### Example user component code 122 | 123 | ```js 124 | import { useSelector } from "react-redux"; 125 | 126 | function Example() { 127 | // The user-provided selector should be memoized with useCallback. 128 | // This will prevent unnecessary re-subscriptions each update. 129 | // This selector can also use e.g. props values if needed. 130 | const memoizedSelector = useCallback(state => state.users, []); 131 | 132 | // The Redux hook will connect user code to useMutableSource. 133 | const users = useSelector(memoizedSelector); 134 | 135 | // ... 136 | } 137 | ``` 138 | 139 | ### Observables 140 | 141 | Observables don’t have an intrinsic version number and so they are incompatible with this API. It might be possible to add a derived version number to an observable, as shown below, but **it would not be safe to do this during render** without causing potential memory leaks. 142 | 143 | ```js 144 | function createBehaviorSubjectWithVersion(behaviorSubject) { 145 | let version = 0; 146 | 147 | const subscription = behaviorSubject.subscribe(() => { 148 | version++; 149 | }); 150 | 151 | return new Proxy(behaviorSubject, { 152 | get: function(object, prop, receiver) { 153 | if (prop === "version") { 154 | return version; 155 | } else if (prop === "destroy") { 156 | return () => subscription.unsubscribe(); 157 | } else { 158 | return object[prop]; 159 | } 160 | } 161 | }); 162 | } 163 | ``` 164 | 165 | # Motivation 166 | 167 | The current best "alternates" to this API are the [Context API](https://reactjs.org/docs/context.html) and [`useSubscription` hook](https://www.npmjs.com/package/use-subscription). 168 | 169 | ### Context API 170 | 171 | The Context API is not currently suited for sources that are used by many components throughout the tree, as changes to the context result in updates that are very heavy (for example, see [Redux v6 performance challenges](https://github.com/reduxjs/react-redux/issues/1177)). (There are currently proposals to improve this: [RFC 118](https://github.com/reactjs/rfcs/pull/118) and [RFC 119](https://github.com/reactjs/rfcs/pull/119).) 172 | 173 | ### `useSubscription` 174 | 175 | [This Gist](https://gist.github.com/bvaughn/054b82781bec875345bd85a5b1344698) outlines the differences between `useMutableSource` and `useSubscription`. The main advantages of this new API are: 176 | 177 | - No temporary tearing will occur during render (even before the initial subscription). 178 | - Subscriptions can be "scoped" so that updates to parts of a mutable source only impact the relevant components (and not all components reading from the source). This means that in the common case, this hook should perform much better. 179 | 180 | # Detailed design 181 | 182 | `useMutableSource` is similar to [`useSubscription`](https://github.com/facebook/react/tree/master/packages/use-subscription). 183 | 184 | - Both require a memoized “config” object with callbacks to read values from an external “source”. 185 | - Both require a way to subscribe and unsubscribe to the source. 186 | 187 | There are some differences though: 188 | 189 | - `useMutableSource` requires the source as an explicit parameter. (React uses this value to protect against "tearing" and ensure that all components reading from a particular source render with the same version of data.) 190 | - `useMutableSource` requires values read from the source to be immutable snapshots. This enables values to be reused during high priority render, allowing more expensive re-renders to be deferred when needed. 191 | 192 | ### Public API 193 | 194 | ```js 195 | type MutableSource = {| 196 | /*…*/ 197 | |}; 198 | 199 | function createMutableSource( 200 | source: Source, 201 | getVersion: () => $NonMaybeType 202 | ): MutableSource { 203 | // ... 204 | } 205 | 206 | function useMutableSource( 207 | source: MutableSource, 208 | getSnapshot: (source: Source) => Snapshot, 209 | subscribe: (source: Source, callback: () => void) => () => void 210 | ): Snapshot { 211 | // ... 212 | } 213 | ``` 214 | 215 | ## Implementation 216 | 217 | ### Root or module scope changes 218 | 219 | Mutable source requires tracking two pieces of info at the module level: 220 | 221 | 1. Work-in-progress version number (tracked per source, per renderer) 222 | 1. Pending update expiration times (tracked per root, per source) 223 | 224 | #### Version number 225 | 226 | Tracking a source's version allows us to avoid tearing when reading from a source that a component has not yet subscribed to. 227 | 228 | In this case, the version should be checked to ensure that either: 229 | 1. This is the first mounting component to read from the source during the current render, or 230 | 2. The version number has not changed since the last read. (A changed version number indicates a change in the underlying store data, which may result in a tear.) 231 | 232 | Like Context, this hook should support multiple concurrent renderers (e.g. ReactDOM and ReactART, React Native and React Fabric). To support this, we will track two work-in-progress versions (one for a "primary" renderer and one for a "secondary" renderer). 233 | 234 | This value should be reset either when a renderer starts a new batch of work or when it finishes (or discards) a batch of work. This information could be stored: 235 | 236 | - On each mutable source itself in a primary and secondary field. 237 | - **Con**: Requires a separate array/list to track mutable sources with outstanding changes. 238 | - At the module level as a `Map` of mutable source to pending primary and secondary version numbers. 239 | - **Con**: Requires at least one extra `Map` structure. 240 | 241 | > ⚠️ **Decision** Store versions directly on the source itself and track pending changes with an array. 242 | 243 | #### Pending update expiration times 244 | 245 | Tracking pending updates per source enables newly-mounting components to read without potentially conflicting with components that read from the same source during a previous render. 246 | 247 | During an update, if the current render’s expiration time is **≤** the stored expiration time for a source, it is safe to read new values from the source. Otherwise a cached snapshot value should be used temporarily1. 248 | 249 | When a root is committed, all pending expiration times that are **≤** the committed time can be discarded for that root. 250 | 251 | This information could be stored: 252 | 253 | - On each Fiber root as a `Map` of mutable source to pending update expiration time. 254 | - On each mutable source as a `Map` of Fiber root to pending update expiration time. 255 | - **Con**: Requires a separate data structure to map roots to mutable sources with outstanding changes (since outstanding changes are cleaned up per-root on commit). 256 | 257 | > ⚠️ **Decision** Store pending update times in a `Map` on the Fiber root. 258 | 259 | 1 Cached snapshot values can't be reused when a config changes between render. More on this below... 260 | 261 | #### A word about why both pending expiration and version are required 262 | 263 | Although useful for updates, pending update expiration times are not sufficient to avoid tearing for newly mounted components even if the source has already been used by another component. Since each component may subscribe to a different part of the store, the following scenario is possible: 264 | 265 | 1. Some components mount and subscribe to source A. 266 | 2. React starts a new render. 267 | 3. A new component (not previously mounted) reads from source A, and then React yields. 268 | 4. Source A changes in a way that does not notify any of the currently-subscribed components, but would impact the new component (which is not yet subscribed). 269 | 5. Another new component (not previously mounted) reads from source A. At this point, there are no pending updates for the source, but it has changed and so reading from it may cause a tear. 270 | 271 | ### Hook state 272 | 273 | The `useMutableSource()` hook’s memoizedState will need to track the following values: 274 | 275 | - The user-provided `getSnapshot` and `subscribe` functions. 276 | - The latest (cached) snapshot value. 277 | - The mutable source itself (in order to detect if a new source is provided). 278 | - The (user-returned) unsubscribe function 279 | 280 | ### Scenarios to handle 281 | 282 | #### Reading from a source before subscribing 283 | 284 | When a component reads from a mutable source that it has not yet subscribed to1, React first checks the version number to see if anything else has read from this source during the current render. 285 | 286 | - If there is a recorded version number (i.e. this is not the first read) does it match the source's current version? 287 | - ✓ If both versions match, the read is **safe**. 288 | - Store the snapshot value on `memoizedState`. 289 | - ✗ If the version has changed, the read is **not safe**. 290 | - Throw and restart the render. 291 | 292 | If there is no version number, the the read **may be safe**. We'll need to next check pending updates for the source to determine this. 293 | 294 | - ✓ If there are no pending updates the read is **safe**. 295 | - Store the snapshot value on `memoizedState`. 296 | - Store the version number for subsequent reads during this render. 297 | - ✓ If the current expiration time is **≤** the pending time, the read is **safe**. 298 | - Store the snapshot value on `memoizedState`. 299 | - Store the version number for subsequent reads during this render. 300 | - ✗ If the current expiration time is **>** the pending time, the read is **not safe**. 301 | - Throw and restart the render. 302 | 303 | 1 This case could occur during a mount or an update (if a new mutable source was read from for the first time). 304 | 305 | #### Reading from a source after subscription 306 | 307 | React will eventually re-render when a source is mutated, but it may also re-render for other reasons. Even in the event of a mutation, React may need to render a higher priority update before processing the mutation. In that case, it’s important that components do not read from a changed source since it may cause tearing. 308 | 309 | In the event the a component renders again without its subscription firing (or as part of a high priority update that does not include the subscription change) it will typically be able to re-use the cached snapshot. 310 | 311 | The one case where this will not be possible is when the `getSnapshot` function has changed. Snapshot selectors that are dependent on `props` (or other component `state`) may change even if the underlying source has not changed. In that case, the cached snapshot is not safe to reuse, and `useMutableSource` will have to throw and restart the render. 312 | 313 | # Design constraints 314 | 315 | - Tearing guarantees are only enforced within a root, for components using the same MutableSource value. Tearing between roots is possible. 316 | - Values read and returned from the store must be immutable in the same way as e.g. class state or props objects. 317 | - e.g. ✓ `getSnapshot: source => Array.from(source.friendIDs)` 318 | - e.g. ✗ `getSnapshot: source => source.friendIDs` 319 | - Values don't need to literally be immutable but should at least be cloned so they are disconnected from the store and are not mutated by changes to the external source. 320 | 321 | * Mutable source must have some form of stable version. 322 | - Version should be global (for the entire source, not parts of the source). 323 | - e.g. ✓ `getVersion: () => source.version` 324 | - e.g. ✗ `getVersion: () => source.user.version` 325 | - Version should change whenever any part of the source is mutated. 326 | - Version does not have to be a number or even a single attribute. 327 | - It can be a serialized form of the data, so long as it is stable and unique. (For example, reading query parameters might treat the entire URL string as the version.) 328 | - It can be the state itself, if the value is immutable (e.g. a Redux store is mutable, but its state is immutable). 329 | 330 | # Alternatives 331 | 332 | See "Motivation" section above. 333 | 334 | # Adoption strategy 335 | 336 | This hook is primarily intended for use by libraries like Redux (and possibly Relay). Work with the maintainers of those libraries to integrate with the hook. 337 | 338 | # How we teach this 339 | 340 | New [reactjs.org](https://reactjs.org/) documentation and blog post. 341 | 342 | # Unresolved questions 343 | 344 | - Are there any common/important types of mutable sources that this proposal will not be able to support? 345 | -------------------------------------------------------------------------------- /text/0212-react-18.md: -------------------------------------------------------------------------------- 1 | - Start Date: 2022-03-23 2 | - RFC PR: https://github.com/reactjs/rfcs/pull/212 3 | - React Issue: N/A 4 | 5 | _Note: This RFC is closer to an "intent to ship" and is different than the 6 | process we typically do because it is the result of years of research into 7 | concurrency, Suspense, and server rendering. All of what is posted here was 8 | designed and discussed over the last year in the React 18 Working Group 9 | ([available here](https://github.com/reactwg/react-18/discussions)) and iterated 10 | on in public experimental releases since 2018. **We'd like to get one final 11 | round of broad public feedback from the community before shipping in case there 12 | are new concerns that have not been discussed before.** You can consider the 13 | Working Group to be a part of the RFC, so please feel free to quote and discuss 14 | any content from it when commenting on the RFC here._ 15 | 16 | # RFC: Intent to ship React 18 17 | 18 | ## Summary 19 | 20 | This RFC describes the changes we intend to ship in React 18 including: 21 | 22 | * New feature: automatic batching 23 | * New feature: transitions 24 | * New Suspense features 25 | * New client and server rendering APIs 26 | * New Strict Mode behaviors 27 | * New hooks 28 | 29 | ## Motivation 30 | 31 | The goal of React 18 is to release the new concurrent rendering APIs that we are building all future React features on top of including Suspense, transitions, streaming server rendering, server components, and more. This release does not include all of the features on day one, but by upgrading your apps fully to React 18 using the new client and server rendering APIs, you will be ready to gradually adopt new future features as they become available. 32 | 33 | While the focus of this release is on upgrading to the new rendering engine, we’re also making a few long-needed improvements (such as automatic batching, Suspense on the server, and `useId`), adding new hooks for libraries to support concurrent rendering (such as `useSyncExternalStore` and `useInsertionEffect`), and a few new concurrent features (such as `useTransition` and `useDeferredValue)`. 34 | 35 | ## Detailed Design 36 | 37 | ### New feature: Automatic Batching 38 | 39 | Batching is when React groups multiple state updates into a single re-render for better performance. Without automatic batching, we only batched updates inside React event handlers. Updates inside of promises, setTimeout, native event handlers, or any other event were not batched in React by default. With automatic batching, these updates will be batched automatically: 40 | 41 | ```js 42 | // Before: only React events were batched. 43 | setTimeout(() => { 44 | setCount(c => c + 1); 45 | setFlag(f => !f); 46 | // React will render twice, once for each state update (no batching) 47 | }, 1000); 48 | 49 | // After: updates inside of timeouts, promises, 50 | // native event handlers or any other event are batched.` 51 | setTimeout(() => { 52 | setCount(c => c + 1); 53 | setFlag(f => !f); 54 | // React will only re-render once at the end (that's batching!) 55 | }, 1000); 56 | ``` 57 | 58 | * [Automatic batching for fewer renders in React 18](https://github.com/reactwg/react-18/discussions/21) 59 | 60 | ### New feature: transitions 61 | 62 | Transitions allow users to tell React that the contained updates are less urgent than other work. For example, handling user text input is more urgent than UI transitions. To support these feature, we’re adding two new APIs: 63 | 64 | * `useTransition`: a hook to start transitions, including a value to track the pending state. 65 | * `startTransition`: a method to start transitions when the hook cannot be used. 66 | 67 | Transitions will opt in to concurrent rendering, which allows the update to be interrupted. If the content re-suspends, transitions also tell React to continue showing the current content while rendering the transition content in the background (see the Suspense RFC for more info). 68 | 69 | * [New feature: startTransition](https://github.com/reactwg/react-18/discussions/41) 70 | * [Real world example: adding startTransition for slow renders](https://github.com/reactwg/react-18/discussions/65) 71 | * [Patterns for startTransition](https://github.com/reactwg/react-18/discussions/100) 72 | 73 | ### New Suspense features 74 | 75 | [These features are outlined in a separate RFC](https://github.com/reactjs/rfcs/pull/213). 76 | 77 | ### New client and server rendering APIs 78 | 79 | In this release we took the opportunity to redesign the APIs we expose for rendering on the client and server. These changes allow users to continue using the old APIs in React 17 mode while they upgrade to the new APIs in React 18. 80 | 81 | On the client, these APIs are: 82 | 83 | * `react-dom/client` 84 | * `createRoot` 85 | * `hydrateRoot` 86 | * `react-dom/server` 87 | * `renderToPipeableStream` 88 | * `renderToReadableStream` 89 | 90 | Additionally, we’ve added limited support for Suspense to: 91 | 92 | * `renderToString` 93 | * `renderToStaticMarkup` 94 | 95 | And have deprecated: 96 | 97 | * `renderToNodeStream` 98 | 99 | Note: these APIs are designed to be upgraded at the same time. For example, upgrading to `createRoot` on the client, but keeping `renderToString` on the server is unsupported. 100 | 101 | * [Replacing render with createRoot](https://github.com/reactwg/react-18/discussions/5) 102 | * [Moving create/hydrateRoot to react-dom/client](https://github.com/reactwg/react-18/discussions/125) 103 | * [Upgrading to React 18 on the client](https://github.com/reactwg/react-18/discussions/6) 104 | * [Upgrading to React 18 on the server](https://github.com/reactwg/react-18/discussions/22) 105 | 106 | ### New Strict Mode behaviors 107 | 108 | In the future, we’d like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React would unmount and remount trees using the same component state as before. 109 | 110 | This feature will give React apps better performance out-of-the-box, but requires components to be resilient to effects being mounted and destroyed multiple times. Most effects will work without any changes, but some effects assume they are only mounted or destroyed once. 111 | 112 | To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount. 113 | 114 | Before this change, React would mount the component and create the effects: 115 | 116 | ``` 117 | * React mounts the component. 118 | * Layout effects are created. 119 | * Effects are created. 120 | ``` 121 | 122 | With Strict Mode in React 18, React will simulate unmounting and remounting the component in development mode: 123 | 124 | ``` 125 | * React mounts the component. 126 | * Layout effects are created. 127 | * Effects are created. 128 | * React simulates unmounting the component. 129 | * Layout effects are destroyed. 130 | * Effects are destroyed. 131 | * React simulates mounting the component with the previous state. 132 | * Layout effects are created. 133 | * Effects are created. 134 | ``` 135 | 136 | * [Adding Strict Effects to Strict Mode](https://github.com/reactwg/react-18/discussions/19) 137 | * [How to support strict effects](https://github.com/reactwg/react-18/discussions/18) 138 | * [Testing with strict effects](https://github.com/reactwg/react-18/discussions/17) 139 | 140 | ### New hooks 141 | 142 | #### useDeferredValue 143 | 144 | `useDeferredValue` is a new hook that accepts a value and returns a new copy of the value that will defer to more urgent updates. If the current render is the result of an urgent update, like user input, React will return the previous value and then render the new value after the urgent render has completed. 145 | 146 | This hook is similar to user-space hooks which use debouncing or throttling to defer updates. The benefits to using `useDeferredValue` is that React will work on the update as soon as other work finishes (instead of waiting for an arbitrary amount of time), and like [`startTransition`](/docs/react-api.html#starttransition), deferred values can suspend without triggering an unexpected fallback for existing content. 147 | 148 | * [New in 18: useDeferredValue](https://github.com/reactwg/react-18/discussions/129) 149 | 150 | #### useSyncExternalStore 151 | 152 | `useSyncExternalStore` is a new hook that allows external stores to support concurrent reads by forcing updates to the store to be synchronous. This new API is recommended for any library that integrates with state external to React. 153 | 154 | [More details on this API are available in a separate RFC.](https://github.com/reactjs/rfcs/pull/214) 155 | 156 | * [Concurrent React for Library Maintainers](https://github.com/reactwg/react-18/discussions/70) 157 | * [useMutableSource → useSyncExternalStore](https://github.com/reactwg/react-18/discussions/86) 158 | 159 | #### useInsertionEffect 160 | 161 | `useInsertionEffect` is a new hook that allows CSS-in-JS libraries to address performance issues of injecting styles in render. Unless you’ve already built a CSS-in-JS library we don’t expect you to ever use this. This hook will run after the DOM is mutated, but before layout effects read the new layout. This solves an issue that already exists in React 17 and below, but is even more important in React 18 because React yields to the browser during concurrent rendering, giving it a chance to recalculate layout. 162 | 163 | * [Library Upgrade Guide: