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

Hydration flash using loadable-components - Did not expect server HTML to contain a <div> in <div> #135

Closed
kirkchris opened this issue Feb 6, 2018 · 12 comments
Labels

Comments

@kirkchris
Copy link

Our app is still experiencing white page flashes when rehydrating from the snapshotted HTML. Originally we believe it was due to not using loadable-components since we had code splitting, but after implementing loadable-components, still seeing the issue.

Here is where we load the app:

import React from 'react';
import { hydrate, render } from 'react-dom';
import { Router, Route, Redirect, applyRouterMiddleware, IndexRoute } from 'react-router';
import { getState, loadComponents } from 'loadable-components';

const rootElement = document.getElementById('main-view');
let renderMethod = render;
if (rootElement.hasChildNodes()) {
  renderMethod = (app, doc) => {
    loadComponents().then(() => hydrate(app, doc))
  };
}

renderMethod(
  <ErrorBoundary>
    <Router history={ParabolaBrowserHistory} render={applyRouterMiddleware(useScroll())}>
      <Route
        path="/"
        component={LoadableComponents.MainContainer}
        onEnter={verify}
      >
     ...
    </Router>
  </ErrorBoundary>,
  rootElement,
);

// react snap state
window.snapSaveState = () => getState();

The LoadableComponents.* are defined like this:

export const MainContainer = loadable(() =>
  import(/* webpackChunkName: "MainContainer" */ './components/MainContainer'),
);

Now I've been able to verify that the state is populating correctly into window from loadable-components and it does call the code and say components are loaded prior to running hydrate. When running on dev react throws this warning - and when breakpointing in it appears to throw it on the rootElement, not a nested element:

Did not expect server HTML to contain a <div> in <div>.

I've checked my snapshotted HTML and my fully rendered (via JS) html after the white flash and the contents inside of rootElement match exactly. My concern there was something was causing a mismatch leading react not to hydrate appropriately.

My guess is that even though the components are done loading via loadable-components, for some reason react is still being rendered once with no content, which is causing the mismatch.

Any ideas for what might cause this? We're using react-router v3.2.0 and react 16.2.0.

@kirkchris kirkchris changed the title Hydration flash using loadable-components - Did not expect server HTML to contain a <div> in <div> Hydration flash using loadable-components - Did not expect server HTML to contain a <div> in <div> Feb 6, 2018
@kirkchris
Copy link
Author

kirkchris commented Feb 6, 2018

Well I dug into this quite a while before opening the issue and now finally was able to fix it just after.

Anyway, this was due to a change needed in react-router v3 to tell it wait to render until it matched the routes appropriately. Documentation here: https://github.com/ReactTraining/react-router/blob/v3/docs/guides/ServerRendering.md

And the working code snippet is this:

import { Router, applyRouterMiddleware, match } from 'react-router';
import routes from './routes'

const rootElement = document.getElementById('main-view');
const renderApp = (renderMethod, renderProps) => {
  renderMethod(
    <ErrorBoundary>
      <Router {...renderProps} render={applyRouterMiddleware(useScroll())} />
    </ErrorBoundary>,
    rootElement,
  );
};

if (rootElement.hasChildNodes()) {
  loadComponents().then(() => {
    match({ history, routes }, (error, redirectLocation, renderProps) => {
      renderApp(hydrate, renderProps);
    });
  });
}
else {
  renderApp(render, { history, routes });
}

This issue can be closed.

@baptooo
Copy link

baptooo commented Jun 14, 2018

Hi @kirkchris I am encountering a flash issue with react-router-dom in version 4.2.2,
could you tell me if your fix is still necessary for this new version please ?

Thank you !

@kirkchris
Copy link
Author

Hi @baptooo I'm not actually sure if this would still be required in v4. v4 is a pretty big update from v3 and we haven't made that upgrade yet. I would check to see if the react-router-dom v4 documentation mentions anything about server rendering like they did for v3!

@baptooo
Copy link

baptooo commented Jul 25, 2018

Thank you for your answer @kirkchris yes I figured out that it because I didn't save my Redux store in the window.snapSaveState function.

No more flash ! 💪

Thank you very much

@maierson
Copy link

Very very nice! Thank you. And yes - still needed with React router 4+.

@stereobooster
Copy link
Owner

And yes - still needed with React router 4+.

I'm not sure you need it. It can happen that we use it different way, but I don't need any workarounds for router only some boilerplate code for loadable-components

@maierson
Copy link

We're not using react-snap nor loadable-components. Just direct import() statements. But it does make sense that the code must be loaded locally on the client before you hydrate in order to prevent flashing. I feel it's more of a subtlety on how React hydrates than anything else. In any case - preloading solved the problem here.

@stereobooster
Copy link
Owner

stereobooster commented Sep 28, 2018

If you're not using react-snap, than use case is different. For those who use it this is my boilerplate

window.snapSaveState = () => getState();

loadComponents()
  .then(() => hydrate(App))
  .catch(() => render(App));

and don't forget to provide { modules: [] } param if you do not use babel-plugin. See gregberge/loadable-components#114

@yyynnn
Copy link

yyynnn commented Nov 20, 2018

Could somebody provide a gist with a full index.js (entry) example?

@stereobooster
Copy link
Owner

Old version https://github.com/stereobooster/an-almost-static-stack/blob/react-snap/src/index.js

@yyynnn
Copy link

yyynnn commented Nov 20, 2018

Cool, thanks, so fast, yeah!
It's still not clear where should user add { modules: [] }

it fails with loadable-components state not found. You have a problem server-side. Please verify that you have called ''loadableState.getScriptTag()'' server-side.

@stereobooster
Copy link
Owner

Can you open separate issue, with description of problem. And specify versions of all libraries

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

No branches or pull requests

5 participants