I have a lot of components, that should displayed separately on different viewports. On client-side, there's useLayoutEffect. Here's how to use it without warning with SSR:
export const useLayoutEffect =
typeof window !== 'undefined' && window.document && window.document.createElement
? React.useLayoutEffect
: React.useEffect;
It's ok if useLayoutEffect runs later after initial DOM render, when the bundle is loaded.
But I need to run it before first paint. It's necessary for conditional rendering using window.matchQuery:
useMedia.tsx:
import { useState } from 'react';
import { useLayoutEffect } from './utils';
export const useMedia = (query: string) => {
const [matches, setMatches] = useState(false);
useLayoutEffect(() => {
const mediaObserver = window.matchMedia(query);
setMatches(mediaObserver.matches);
const updateMatch = (e: MediaQueryListEvent) =>
e.matches ? setMatches(true) : setMatches(false);
if (mediaObserver.addEventListener) {
mediaObserver.addEventListener('change', updateMatch);
return () => mediaObserver.removeEventListener('change', updateMatch);
} else {
// Safari < 14
mediaObserver.addListener(updateMatch);
return () => mediaObserver.removeListener(updateMatch);
}
}, [query]);
return matches;
};
Another identical case is when I need to toogle the state before the DOM is rendered.
useLayoutEffect(() => {
if (window.matchMedia('(max-width: 1024px)').matches) {
setOpenInfo(false);
}
}, []);
Which is not possible for SSR before initial render. For now I can use https://github.com/artsy/fresnel package, which seems to solve the problem, or in the future features like HTML Client Hints. Maybe there's some better approach I missed?