astro icon indicating copy to clipboard operation
astro copied to clipboard

🐛 BUG: useLayoutEffect causes error overlay in dev server

Open mayank99 opened this issue 3 years ago • 10 comments

What version of astro are you using?

1.0.0-rc.8

Are you using an SSR adapter? If so, which one?

None

What package manager are you using?

npm

What operating system are you using?

Mac


Describe the Bug

Problem: Using useLayoutEffect in a React component shows the following error in an overlay in dev server:

TypeError: Cannot read properties of null (reading 'useLayoutEffect')
    at Proxy.useLayoutEffect (/home/projects/github-nw7vut-bejbg2/node_modules/react/cjs/react.development.js:1637:21)
    at __vite_ssr_exports__.default (/src/MyReactComponent.tsx:10:25)
    at Tester (@astrojs/react/server.js:38:18)
Recording from `1.0.0-beta.70`

https://user-images.githubusercontent.com/9084735/179028389-ce4926aa-c91e-45f4-92a8-a171c298c9a3.mov


Expectation: I understand that effects cannot run outside the browser, but it should just produce a warning instead of crashing completely. When using external components, it is often not possible to convert those useLayoutEffects into useEffects, so this crash makes it hard to use them. Next.js (which also uses SSG) is able to handle these same components without any crashes.


Workaround: ~~I can avoid the crash using client:only, but obviously this is not ideal, as the markup is not included in the initial response.~~ As of rc.8 the error is exclusive to the dev server, so it doesn't break prod build.


Link to Minimal Reproducible Example

https://stackblitz.com/edit/github-nw7vut-bejbg2?file=src%2FMyReactComponent.tsx

Participation

  • [ ] I am willing to submit a pull request for this issue.

mayank99 avatar Jul 14 '22 16:07 mayank99

Thanks for the report, @mayank99! This is definitely something we need to support, I suspect this is a problem with our React renderer.

natemoo-re avatar Jul 14 '22 21:07 natemoo-re

I'll take this (so I can be assigned)

bluwy avatar Aug 05 '22 13:08 bluwy

Debugging this today, from what I can tell, Vite's SSR seems to be working well, but something's fishy within React that seems to be calling useLayoutEffect twice (once during SSR, twice during stack frame generation to warn about:)

Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://reactjs.org/link/uselayouteffect-ssr for common fixes.

I believe this only happens when streaming, hence it only started since beta.55 (https://github.com/withastro/astro/pull/3696). Here are the entire stack trace that shows useLayoutEffect is being called twice:

click to expand
Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://reactjs.org/link/uselayouteffect-ssr for common fixes.
    at Tester
/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react/cjs/react.development.js:1642
  return dispatcher.useLayoutEffect(create, deps);
                    ^

TypeError: Cannot read properties of null (reading 'useLayoutEffect')
    at Proxy.useLayoutEffect (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react/cjs/react.development.js:1642:21)
    at __vite_ssr_exports__.default (/src/MyReactComponent.tsx:10:25)
    at Tester (@astrojs/react/server.js:38:18)
    at describeNativeComponentFrame (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:3732:7)
    at describeFunctionComponentFrame (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:3827:12)
    at getStackByComponentStackNode (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5294:19)
    at getCurrentStackInDEV (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5460:12)
    at Object.ReactDebugCurrentFrame.getStackAddendum (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react/cjs/react.development.js:130:16)
    at printWarning (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:57:40)
    at error (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:47:7)
    at Object.useLayoutEffect (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5163:5)
    at Proxy.useLayoutEffect (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react/cjs/react.development.js:1642:21)
    at __vite_ssr_exports__.default (/src/MyReactComponent.tsx:10:25)
    at Tester (@astrojs/react/server.js:38:18)
    at renderWithHooks (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5662:16)
    at renderIndeterminateComponent (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5735:15)
    at renderElement (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:5950:7)
    at renderNodeDestructiveImpl (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6108:11)
    at renderNodeDestructive (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6080:14)
    at retryTask (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6532:5)
    at performWork (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6580:7)
    at /Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6904:12
    at scheduleWork (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:78:3)
    at startWork (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:6903:3)
    at renderToNodeStreamImpl (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:7048:3)
    at Object.renderToStaticNodeStream (/Users/bjorn/Work/repros/astro-react-ssr/node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js:7061:10)
    at eval (@astrojs/react/server.js:128:46)
    at new Promise (<anonymous>)
    at renderToStaticNodeStreamAsync (@astrojs/react/server.js:127:9)
    at async renderToStaticMarkup (@astrojs/react/server.js:91:11)
    at async Object.check (@astrojs/react/server.js:51:2)
    at async Module.renderComponent (/node_modules/astro/dist/runtime/server/index.js:182:13)

The stack is operating in the react renderer's check function's Tester component. I'm not quite sure why this happens, but perhaps a way to fix this is to ignore the second error. I also think this might be a bug in React.

bluwy avatar Aug 08 '22 08:08 bluwy

I believe this only happens when streaming, hence it only started since beta.55

Just wanted to mention that it also did happen before beta.55. The difference is that beta.55 shows the error in an overlay whereas beta.70 crashes the dev server.

However, I noticed that rc.7 doesn't crash the dev server anymore (it shows the error overlay, same as beta.55) and it also seems to work fine in prod build, albeit with a "Invalid hook call" warning.

mayank99 avatar Aug 08 '22 11:08 mayank99

Thanks for checking it @mayank99! Looks like indeed it's working as before now. I think the warnings would be preserved for now so it's expected in this case. I'll go ahead and close this as resolved.

bluwy avatar Aug 08 '22 14:08 bluwy

maybe i'm misunderstanding but why is the overlay considered expected behavior? shouldn't it just be a console warning?

mayank99 avatar Aug 08 '22 15:08 mayank99

The overlay is showed for any errors happening on the server (IIRC), so if we want to hide the overlay, we'd need specific handling for useLayoutEffect. I suppose with React intentionally throwing a non-suppresable error, the goal is to have users to not use useLayoutEffect for SSR, so I think a (perhaps annoying) overlay could be good in the long run.

bluwy avatar Aug 09 '22 08:08 bluwy

The overlay is showed for any errors happening on the server (IIRC)

But it's supposed to be a warning, not an error (at least from the wording). And other SSR tools like nextjs handle this just fine. Here's an example with vite-plugin-ssr: https://stackblitz.com/edit/github-fj233q?file=pages%2Findex%2FCounter.jsx

Now if React was throwing an error instead of just logging it in the console, I think the overlay would totally make sense. But that's not the case here.

mayank99 avatar Aug 09 '22 14:08 mayank99

You're right. Looks like the error is indeed from the second call from https://github.com/withastro/astro/issues/3923#issuecomment-1207832056, not from React. My bad!

In rc.7 we returned to the non-breaking way that doesn't kill the server/build, so I thought this should be fine. But yeah I think it's still nice to investigate this.

I discussed with @matthewp yesterday and it seems like the core issue has something to do with React streaming api throwing errors within itself while it's trying to generate the stack trace for the warning. As you can see from the logs, it has at Tester, but the rest errors out.

I'll re-open this at the meantime until I (or anyone) can repro this to be a pure React streaming bug, or if it's indeed an Astro bug.

bluwy avatar Aug 09 '22 15:08 bluwy

Thanks for reopening. I've updated the issue title to more accurately reflect the problem in the latest RC.

mayank99 avatar Aug 09 '22 15:08 mayank99

Tried to create a React repro today without success. I did notice though, if you export your component as a named function instead like export default function Component() {, the overlay goes away, and the warning only prints in the console (although a few times in a row).

I think this might be the best workaround for now. And it's a better practice too as React Refresh works best with named functions. It's a bit hard for me to invest more time in digging out the React streaming issue. Would this work for your case?

bluwy avatar Aug 12 '22 09:08 bluwy