-
Notifications
You must be signed in to change notification settings - Fork 561
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
shouldComponentHydrate #46
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
- Start Date: 2018-04-27 | ||
- RFC PR: | ||
- React Issue: | ||
|
||
# Summary | ||
|
||
This RFC seeks to add the capability for a component subtree to opt out of rehydration diffing. | ||
|
||
# Basic example | ||
|
||
I propose adding a new lifecycle method: `shouldComponentHydrate`. Returning `false` from this method should cause React to opt out of reconciliation during rehydration and leave the subtree as-is. | ||
|
||
```jsx | ||
function getComponent() { | ||
return ( | ||
typeof window === 'undefined' | ||
? require('./foo').default | ||
: import('./foo').then(mdl => mdl.default) | ||
); | ||
} | ||
|
||
class CodeSplittingComponent extends React.Component { | ||
constructor() { | ||
this.state = { | ||
component: getComponent(), | ||
}; | ||
|
||
if (this.state.component instanceof Promise) { | ||
this.state.component.then(ReactClass => this.setState({ component: ReactClass })); | ||
} | ||
} | ||
|
||
shouldComponentHydrate() { | ||
return false; | ||
} | ||
|
||
/** | ||
* Block re-rendering until the component is resolved and ready. | ||
*/ | ||
shouldComponentUpdate(props, state) { | ||
return !(state.component instanceof Promise); | ||
} | ||
|
||
render() { | ||
return ( | ||
<this.state.component /> | ||
); | ||
} | ||
} | ||
``` | ||
|
||
# Motivation | ||
|
||
The basic goal here is to allow something to be rendered by the server and remain untouched by React until the component enqueues a subsequent rerender via prop or state change. | ||
|
||
When working in a code splitting context, this becomes very important because what is immediately available on the server may not be on the client. A specific example of a typical SSR + code splitting flow: | ||
|
||
1. ReactDOMServer renders a complete page to string and sends it to the browser via some means. | ||
|
||
2. React in the browser rehydrates, but one or more component children in the tree are meant to be asynchronously-loaded in the browser context. | ||
|
||
3. When the code splitting component is mounted, this temporarily causes the pre-rendered subtree to be discarded since the asynchronously-loaded child has not yet been downloaded. | ||
|
||
4. When the child JS downloads, the code splitting component then enqueues a re-render and the child appears again. | ||
|
||
Note the UX problem in step 3: pre-rendered HTML ends up being thrown out because there is no current way to opt out of reconciliation during rehydration. | ||
|
||
# Detailed design | ||
|
||
My proposal is to add a new lifecycle method called `shouldComponentHydrate(props)`. It should act similarly to `shouldComponentUpdate`, but be run during the rehydration phase. If `false` is returned from the method, React should halt reconciliation on this part of the subtree and move on. | ||
|
||
When returning `false` from `shouldComponentUpdate`, `componentDidMount` should not be fired until a subsequent render causes the component subtree to be initialized for the first time. | ||
|
||
Note that `shouldComponentHydrate` does not prevent future re-renders. It should only opt out of rehydration. Subsequent renders due to a change in props or state would need to be managed via `shouldComponentUpdate` if further reconciliation control is desired. | ||
|
||
# Drawbacks | ||
|
||
Some added complexity to how reconciliation works in the rehydration context. | ||
|
||
# Alternatives | ||
|
||
TBD | ||
|
||
# Adoption strategy | ||
|
||
This is an entirely new feature, so backward compatibility is not a concern. It's purely an opt-in optimization for some specific use cases that are not currently solvable in userland. | ||
|
||
# How we teach this | ||
|
||
Adding it to the React documentation alongside the other lifecycle methods should be sufficient. A blog post introducing it would probably also make sense. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can it be a static class property instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could be, but that would make the functionality less flexible. I really enjoy the potential symmetry with the
shouldComponentUpdate
lifecycle and was trying to keep usage roughly equivalent (providing a function that returns a boolean based on props.)