udecode / plate

A rich-text editor powered by AI
https://platejs.org
Other
11.73k stars 713 forks source link

serializeHTMLFromNodes logs useLayoutEffect warning in NextJS #1047

Closed seloner closed 6 months ago

seloner commented 3 years ago

Description

serializeHTMLFromNodes cause warnings in nextjs

const html = React.useMemo(() => {
    return serializeHTMLFromNodes(editor, {
      plugins,
      nodes: debugValue
    });
  }, [debugValue, editor, plugins]);

I am getting this warning

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

Environment

Funding

Fund with Polar

bensquire commented 3 years ago

We also have the same issue using MeteorJS and React.

rhlc commented 3 years ago

Getting this error a Next.js project as well.

dylans commented 3 years ago

serializeHTMLFromNodes depends slate's useIsomorphicLayoutEffect: https://github.com/ianstormtaylor/slate/blob/main/packages/slate-react/src/hooks/use-isomorphic-layout-effect.ts which relies on this logic to determine if it's running in a DOM environment or not: https://github.com/ianstormtaylor/slate/blob/main/packages/slate-react/src/utils/environment.ts#L48-L51

Another possible culprit for this warning is the dependency on https://github.com/udecode/plate/blob/main/packages/serializers/html/src/serializer/serializeHTMLFromNodes.ts#L1

Another common cause of this issue is having something that defines window (e.g. Jest or jsdom iirc).

It could also be caused by one of many other dependencies. But overall it does not appear to be something that Plate is causing deliberately (though it's possible I've missed something of course).

A semi-dirty way to solve this in Next.js environments is something like if (!process.browser) React.useLayoutEffect = React.useEffect;

henry-infevo commented 2 years ago

I found the warning happens on client because the serializeHTMLFromNodes function is using React.renderToStaticMarkup . This function throws the warning. https://github.com/facebook/react/blob/main/packages/react-dom/src/server/ReactPartialRendererHooks.js#L411

wfischer42 commented 2 years ago

To add to it, serializeHtml (renamed?) seems to be passing a new Slate element initialized with the value we want to serialize into the renderStaticMarkup call. The renderStaticMarkup function is from react-dom-server instead of react-dom, so the dispatcher for useLayoutEffect targets the server package instead of the client package, even though it's executing on the client. That's why the CAN_USE_DOM or other typeof window... checks don't prevent the issue.

So when this Slate element (which seems to be rendered in a server module on the client side) sets useIsomorphicLayoutEffect, it's passing the client-side check, but React dispatches the server-side useLayoutEffect function through react-server-dom, which is just a placeholder that prints that error.

I can't think of a great solution. The simplest solution would be to add an option to Slate that allows you to force useIsomorphicLayoutEffect to point to useEffect, then pass in that flag when calling renderStaticMarkup. Otherwise, I wonder if it would work to use slate-react-presentation or some other method to generate the element that goes into the renderStaticMarkup call?

Thoughts @zbeyens?

zbeyens commented 2 years ago

Makes a lot of sense.

renderStaticMarkup was used as the quickest DRY solution, I'm open to any other alternatives. Otherwise I'm sure @dylans would quickly review your suggested option to Slate.

wfischer42 commented 2 years ago

@zbeyens, when serializing from the contents of an existing Slate/Plate instance (instead of an editor data object), what about applying a simple container ref:

<Plate renderEditable={(children) => (
    <div ref={editorContents}>
      {children}
    </div>
  )} 
/>

...and normalizing the HTML string from editorContents.current.innerHtml? It seems like it would mostly be a matter of stripping out Slate <span> tags and removing unwanted classnames. Would that be viable?

If that would work, it might also be a suitable alternate strategy for renderStaticMarkup while serializing a raw data object as well.

zbeyens commented 2 years ago

Worth a try. How would we use it server-side?