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.
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).
<ThemeProviders />
componentYou 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.
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.
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!