Do you want to request a feature or report a bug?
Feature
What is the current behavior?
Hydrating some server-rendered content can be difficult, inefficient or impossible. For example, in the process of rendering on the server, significant work or additional data may be required for data processing and conversion, such as custom templating or localization. The content can be large too, such as product information or a news article.
When the resulting content is highly dynamic and changes with state, there is no choice but recreate it within React paradigm and recreate it on client. However, complicated server-generated content is often (if not typically) static. Delivering a redundant copy of static content to client just to compare and ignore it during hydration seems a waste of resources and can be prohibitively expensive.
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:
https://codesandbox.io/s/zx38ow3z8x
What is the expected behavior?
Instead of additional complications of recreating it on the client with hydration, wouldn鈥檛 it be much easier to just accept the content from server as-is and tell hydrate() to leave it alone?
There may be a few options for non-hydrating SSR:
shouldComponentHydrate() to disable hydration of component contentexport default class NoHydrate extends Component {
// return false to avoid re-rendering of this component in hydrate()
shouldComponentHydrate() {
return false;
}
render() {
// on server, simply render content
// on client, this is never called and server content is accepted as-is
return (
<div>
{this.props.children}
</div>
);
}
}
render()export default class AutoHydrate extends Component {
render() {
// on server, simply render content
// on client, find SSR in dom and re-render using dangerouslySetInnerHTML
// ** requires a unique id, generated before or during server rendering **
return (typeof window === 'undefined') ? (
<div id={this.props.id}>
{this.props.children}
</div
) : (
<div id={this.props.id}
dangerouslySetInnerHTML={{
__html: document.getElementById(this.props.id).innerHTML
}}
/>
);
}
}
dangerouslySetInnerHTML with empty contentexport default class Ssr extends Component {
render() {
// on server, simply render content
// on client, render empty content using dangerouslySetInnerHTML,
// which normally causes a warning of content mismatch and keeps the existing content
// also add suppressHydrationWarning to turn off the warning.
return (typeof window === 'undefined') ? (
<div>
{this.props.children}
</div>
) : (
<div
dangerouslySetInnerHTML={{
__html: ''
}}
suppressHydrationWarning
/>
);
}
}
Considering that there is a way to make it work now, documenting (3) may be all that needs to happen. However if (1) could be added with same behavior, it would look cleaner.
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
Version 16.2. Not sure if the working option 3 has worked before or is supposed to work in future versions.
I think this should probably go to https://github.com/reactjs/rfcs
I'll close as duplicate of https://github.com/facebook/react/issues/10923. There's a hacky solution in https://github.com/facebook/react/issues/10923#issuecomment-338715787 (same as you described).
I think reopening this issue could valuable since targeted hydration is an exciting approach to client performance. Depending on the use case, it can mean shipping a smaller bundle and removing the need for hydration in many cases.
I made a proof of concept at https://github.com/kirkstrobeck/hydrator
I'm using:
<StaticContent><MyComponent /></StaticContent>
import { createElement, useRef, useState, useEffect } from 'react'
function useStaticContent() {
const ref = useRef(null)
const [render, setRender] = useState(typeof window === 'undefined')
useEffect(() => {
// check if the innerHTML is empty as client side navigation
// need to render the component without server-side backup
const isEmpty = ref.current.innerHTML === ''
if (isEmpty) {
setRender(true)
}
}, [])
return [render, ref]
}
export default function StaticContent({ children, element = 'div', ...props }) {
const [render, ref] = useStaticContent()
// if we're in the server or a spa navigation, just render it
if (render) {
return createElement(element, {
...props,
children,
})
}
// avoid re-render on the client
return createElement(element, {
...props,
ref,
suppressHydrationWarning: true,
dangerouslySetInnerHTML: { __html: '' },
})
}
Most helpful comment
I'm using: