Skip to content
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

Support partial hydration for static content #12617

Closed
ghost opened this issue Apr 15, 2018 · 4 comments
Closed

Support partial hydration for static content #12617

ghost opened this issue Apr 15, 2018 · 4 comments

Comments

@ghost
Copy link

ghost commented Apr 15, 2018

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’t 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:

  1. Add a new callback shouldComponentHydrate() to disable hydration of component content
export 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>
        );
    }
}
  1. Access SSR content from DOM in render()
    This is probably the worst option, though it is the only one that definitely works currently.
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
                }}
            />
        );
    }
}
  1. Use dangerouslySetInnerHTML with empty content
    It actually works now, but it is not documented that it is supposed to.
export 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.

@quantizor
Copy link
Contributor

I think this should probably go to https://github.com/reactjs/rfcs

@gaearon gaearon changed the title Need support for automatic hydration of static server-rendered content. Support partial hydration for static content Aug 2, 2018
@gaearon
Copy link
Collaborator

gaearon commented Nov 1, 2018

I'll close as duplicate of #10923. There's a hacky solution in #10923 (comment) (same as you described).

@gaearon gaearon closed this as completed Nov 1, 2018
@kirkstrobeck
Copy link

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

@aralroca
Copy link

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: '' },
  })
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants