简体   繁体   中英

How to fix "Warning: useLayoutEffect does nothing on the server" while using Material UI and reactDOMServer?

I'm having a issue with ReactDOMServer and Material UI Theme Provider.. Everything is working just fine but I keep getting this annoying error on console:

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.

All the solutions I've found so far involve me having to remove ThemeProvider, but I would like to know if there is no way better to solve this?

CODE:

  const mapIcon = useMemo(() => {
    let namePosition: MapPinProps['namePosition'] = 'bottom-start';

    if (position.lng > 120) {
      namePosition = 'bottom-end';
    }

    if (position.lng > 120 && position.lat < -80) {
      namePosition = 'top-end';
    }

    if (position.lng <= 120 && position.lat < -80) {
      namePosition = 'top-start';
    }

    const html = ReactDOMServer.renderToStaticMarkup(
      <ThemeProviders>
        <MapPin
          active={isMapBuilder ? active : !completed}
          highlight={highlightRequiredEvent}
          icon={completed && doneIcon ? doneIcon : icon}
          name={event?.type !== EVENTS_TYPE.ANIMATION || isMapBuilder ? name : ''}
          rarity={rarity}
          read={isMapBuilder || event?.type === EVENTS_TYPE.ANIMATION || read}
          interactive={isMapBuilder || event?.type !== EVENTS_TYPE.ANIMATION}
          selected={selected}
          shape={shape}
          size={iconSize}
          userSettings={user.settings}
          namePosition={namePosition}
          locked={locked && !isMapBuilder}
          isMapBuilder={isMapBuilder}
        />
      </ThemeProviders>
    );

    return new L.DivIcon({
      className: '',
      // iconAnchor,
      // popupAnchor,
      iconSize: [iconSize, iconSize],
      html: html.toString(),
    });
  }, [
    position.lng,
    position.lat,
    event?.type,
    isMapBuilder,
    active,
    completed,
    highlightRequiredEvent,
    doneIcon,
    icon,
    name,
    rarity,
    read,
    selected,
    shape,
    iconSize,
    user.settings,
    locked,
  ]);

I'll try to give some options that could help on solving that. I'll make some assumptions here and there, allowing myself to be wrong in some cases.

Context

useLayoutEffect() only fires after DOM mutations . In general, servers don't have a proper way of handling that. Also, the official doc also mentions:

If you use server rendering, keep in mind that neither useLayoutEffect nor useEffect can run until the JavaScript is downloaded. This is why React warns when a server-rendered component contains useLayoutEffect.

and gives a way of fixing it but it requires access to the source code (which I suppose you don't have).

Assumptions

  • You are using Server Side Rendering to build Components at the Backend
  • You are also using React at the Client (Frontend)
  • You are using Leaflet for Maps (because L.DivIcon() )
  • You don't have access to change the source code of the <ThemeProviders /> component

Possible Solution #1 (Possibly best one?)

You could try to use ReactDOMServer.renderToString() instead of ReactDOMServer.renderToStaticMarkup() .

Explanation: Supposing that you are using this on the server and using React on the Client, according to the official docs about ReactDOMServer.renderToStaticMarkup() :

If you plan to use React on the client to make the markup interactive, do not use this method. Instead, use renderToString on the server and ReactDOM.hydrateRoot() on the client.

Possible Solution #2 (With access to ThemeProviders component source code)

You could change the check for the usage of useLayoutEffect() and prevent it from using it at the server, where you don't have any DOM.

Explanation: According to this comment inside an Issue at GitHub , it suggest choosing between useLayoutEffect() and useEffect() based on the existence of the DOM. The comment mention about using something like:

const canUseDOM: boolean = !!(
  typeof window !== 'undefined' &&
  typeof window.document !== 'undefined' &&
  typeof window.document.createElement !== 'undefined'
);

const useIsomorphicLayoutEffect = canUseDOM ? useLayoutEffect : useEffect;

And you could use this useIsomorphicLayoutEffect() inside your component.

Possible Solution #3 (not use SSR)

You could consider removing the usage of the ReactDOMServer .

Explanation: This seems to be a very simple component and the cost of choosing to render it on Client could be insignificant compared to the server render. NOTE: Before choosing to remove the SSR you could base this decision on some benchmarks and tests.


I hope that some of this could help you!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM