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:and gives a way of fixing it but it requires access to the source code (which I suppose you don't have).
Assumptions
<ThemeProviders />componentPossible 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():
Possible Solution #2 (With access to
ThemeProviderscomponent 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()anduseEffect()based on the existence of the DOM. The comment mention about using something like: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!