react-activation icon indicating copy to clipboard operation
react-activation copied to clipboard

no match between SSR'ed Dom structure and rehydrated

Open khanbaba opened this issue 5 years ago • 19 comments

here is code of my page:

render() {
    const {
      query: { random_key }
    } = this.props
    return (
      <KeepAlive id={random_key} name={`p/${random_key}`}>
        <ScrollPosition>
          <Product {...this.props} />
        </ScrollPosition>
      </KeepAlive>
    )
  }

Rehydrated page must matches the original SSR'ed page otherwise react will recreate DOM and as a side effect core web vitals metrics like LCP will be measured after client-side rehydration.

khanbaba avatar Aug 31 '20 15:08 khanbaba

this is console error of this problem: rehydration-problem

implementation environment: reactjs: 16.13.1 nextjs: 9.4.4 react-activation: 0.4.2

khanbaba avatar Aug 31 '20 15:08 khanbaba

I also checked react-activation version 0.5.5 and the problem exists.

khanbaba avatar Sep 02 '20 05:09 khanbaba

I have almost no experience with React SSR hydrate, so I just made a simple SSR compatibility in react-activation

The implementation of react-activation determines that it may not be able to guarantee that the content rendered by the client and server is consistent when the application is initialized.

Do you have any idea on this issue?

CJY0208 avatar Sep 02 '20 06:09 CJY0208

please read this post: https://joshwcomeau.com/react/the-perils-of-rehydration/ you should remove some stochastic conditions in the code.

khanbaba avatar Sep 02 '20 06:09 khanbaba

same issue on react-keep-alive: https://github.com/StructureBuilder/react-keep-alive/pull/85

khanbaba avatar Sep 05 '20 16:09 khanbaba

Hi @CJY0208, can you please explain briefly how you added SSR to react-keep-alive library. I have to fix this issue if you write some help about the implementation.

khanbaba avatar Sep 12 '20 05:09 khanbaba

I confirm this is an issue, still forced to use react-keep-alive due to this issue.

AlexSapoznikov avatar Oct 28 '20 08:10 AlexSapoznikov

I confirm this is an issue, still forced to use react-keep-alive due to this issue.

Hi @AlexSapoznikov I have decided to not use react-keep-alive and i have used another better solution to keep my pages alive in react.

khanbaba avatar Oct 28 '20 08:10 khanbaba

I confirm this is an issue, still forced to use react-keep-alive due to this issue.

Hi @AlexSapoznikov I have decided to not use react-keep-alive and i have used another better solution to keep my pages alive in react.

Hi @khanbaba So what are you using, if I may ask?

AlexSapoznikov avatar Oct 28 '20 09:10 AlexSapoznikov

I confirm this is an issue, still forced to use react-keep-alive due to this issue.

Hi @AlexSapoznikov I have decided to not use react-keep-alive and i have used another better solution to keep my pages alive in react.

Hi @khanbaba So what are you using, if I may ask?

read the solution here: https://stackoverflow.com/questions/59124463/nextjs-how-to-not-unmount-previous-page-when-going-to-next-page-to-keep-state

khanbaba avatar Oct 28 '20 09:10 khanbaba

I confirm this is an issue, still forced to use react-keep-alive due to this issue.

Hi @AlexSapoznikov I have decided to not use react-keep-alive and i have used another better solution to keep my pages alive in react.

Hi @khanbaba So what are you using, if I may ask?

read the solution here: https://stackoverflow.com/questions/59124463/nextjs-how-to-not-unmount-previous-page-when-going-to-next-page-to-keep-state

Thanks! Will look into it.

AlexSapoznikov avatar Oct 28 '20 10:10 AlexSapoznikov

有进展吗? @CJY0208

luhc228 avatar Sep 22 '22 11:09 luhc228

@CJY0208 cc

出问题应该是这里,在 server 端和 client 端分别使用了不同的组件进行渲染,导致了出现节点匹配不一致的问题。 其实这里不需要环境的判断,直接使用 expandKeepAlive(withActivation(KeepAlive)) 在双端渲染也是 ok 的 https://github.com/CJY0208/react-activation/blob/8c3161ec1f01ed06f344620a3c156ac2a1037aae/src/core/KeepAlive.js#L271

我 debug 到这里的时候大概发现的是,react 在进行 hydrate 的时候,就出来了节点 mismatch 的情况了

image

复现路径: 可以在 feat/keep-alive-plugin 这个分支上,执行 npm run setup 脚本安装依赖后,然后 cd example/with-keep-alive 执行 npm start 即可复现问题了

参考:https://github.com/facebook/react/issues/24384

luhc228 avatar Sep 23 '22 06:09 luhc228

意思就是 keep alive 组件的 ssr 和 csr 逻辑不一致所以报错了

但现在面临一个问题,keep alive 的原理本来就是先渲染到别处再 dom 移回来的

ssr 没有 dom 所以是直接渲染的

如果要 ssr 也和 csr 保持一致,那可能导致 ssr 的产物里,KeepAlive 内部的视图显示不出来

CJY0208 avatar Sep 23 '22 07:09 CJY0208

意思就是 keep alive 组件的 ssr 和 csr 逻辑不一致所以报错了

但现在面临一个问题,keep alive 的原理本来就是先渲染到别处再 dom 移回来的

ssr 没有 dom 所以是直接渲染的

如果要 ssr 也和 csr 保持一致,那可能导致 ssr 的产物里,KeepAlive 内部的视图显示不出来

ssr 只关心第一次渲染的结果,后面视图更新都是由 csr 承接的。你现在只需要保证 ssr KeepAlive 内的内容出来就行,事实上是可以的。

luhc228 avatar Sep 23 '22 07:09 luhc228

现在可见的修复措施,产生的结果可能是 ssr 首屏的内容里,KeepAlive 标签包裹的部分会是空白的,这个感觉是不可接受的吧

CJY0208 avatar Sep 23 '22 08:09 CJY0208

现在可见的修复措施,产生的结果可能是 ssr 首屏的内容里,KeepAlive 标签包裹的部分会是空白的,这个感觉是不可接受的吧

什么情况下是空白的?目前我用的时候没有空白

luhc228 avatar Sep 23 '22 08:09 luhc228

Not a great solution, but if you're willing to sacrifice an extra re-render here's a hack for NextJS.

Create a KeepAlive wrapper component:

import { KeepAlive as _KeepAlive } from "react-activation";
import React, { FC, ReactNode, useEffect, useState } from "react";

export interface KeepAliveProps {
  children: ReactNode;
}

const KeepAlive: FC<KeepAliveProps> = ({ children }: KeepAliveProps) => {
  const [keepAliveChildren, setKeepAliveChildren] = useState(<>{children}</>);

  useEffect(() => {
    setKeepAliveChildren(<_KeepAlive saveScrollPosition="screen">{children}</_KeepAlive>);
  }, []);

  return <>{keepAliveChildren}</>;
};

export default KeepAlive;

and then use it like this:

import KeepAlive from "@client/components/common/shared/keep-alive";
import React, { FC } from "react";

const IndexPage: FC = () => {
  return (
    <KeepAlive>
      <div>Hello World!</div>
    </KeepAlive>
  );
};

export default IndexPage;

nfrederick023 avatar Dec 16 '23 04:12 nfrederick023

Not a great solution, but if you're willing to sacrifice an extra re-render here's a hack for NextJS.

Create a KeepAlive wrapper component:

import { KeepAlive as _KeepAlive } from "react-activation";
import React, { FC, ReactNode, useEffect, useState } from "react";

export interface KeepAliveProps {
  children: ReactNode;
}

const KeepAlive: FC<KeepAliveProps> = ({ children }: KeepAliveProps) => {
  const [keepAliveChildren, setKeepAliveChildren] = useState(<>{children}</>);

  useEffect(() => {
    setKeepAliveChildren(<_KeepAlive saveScrollPosition="screen">{children}</_KeepAlive>);
  }, []);

  return <>{keepAliveChildren}</>;
};

export default KeepAlive;

and then use it like this:

import KeepAlive from "@client/components/common/shared/keep-alive";
import React, { FC } from "react";

const IndexPage: FC = () => {
  return (
    <KeepAlive>
      <div>Hello World!</div>
    </KeepAlive>
  );
};

export default IndexPage;

The new Problem.

app-index.js:32 Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to `this.state` directly or define a `state = {};` class property with the desired state in the ProviderBridge component.

chenweigh avatar Dec 19 '23 10:12 chenweigh