В течение долгого времени я использую собственный крючок реагирования под названием useIsMobile
, который возвращает true, если область просмотра имеет определенную ширину. Я использовал этот крючок во всех своих проектах, и у меня никогда не было с ним проблем.
Недавно я начал новый проект, используя Next 14
, и начал получать эту ошибку:
Error: Text content does not match server-rendered HTML. See more info here: https://nextjs.org/docs/messages/react-hydration-error
После некоторой отладки я понимаю, что проблема связана с моим специальным хуком. Когда я использую перехватчик, сервер сначала отправляет «мобильную версию», но клиент отображает «версию для настольного компьютера» так, как должен, и создает разницу между деревом реагирования, которое было предварительно визуализировано с сервера, и деревом реагирования, которое было визуализировано во время первого рендеринга.
Эта проблема сохраняется, даже если я использую «использовать клиент». Я также заметил, что иногда ошибка может быть вызвана медиазапросами CSS или может быть полностью проигнорирована.
Кто-нибудь еще сталкивался с этой проблемой? Кто-нибудь знает, как я могу это исправить?
Это крючок, который я использую:
'use client';
import { useState, useEffect } from 'react';
const useWindowSize = () => {
const [windowSize, setWindowSize] = useState({
width: typeof window !== 'undefined' ? window?.innerWidth : null,
height: typeof window !== 'undefined' ? window?.innerHeight : null,
});
useEffect(() => {
const handleResize = () => {
if (typeof window !== 'undefined') {
setWindowSize({
width: window?.innerWidth,
height: window?.innerHeight,
});
}
};
window?.addEventListener('resize', handleResize);
handleResize();
return () => window?.removeEventListener('resize', handleResize);
}, []);
return windowSize;
};
export default useWindowSize;
'use client';
import { useState, useEffect } from 'react';
import useWindowSize from './useWindowSize';
const useIsMobile = (breakpoint: number = 768): boolean => {
const { width } = useWindowSize()!;
const [isMobile, setMobile] = useState<boolean>(
width! <= (breakpoint || 768)
);
useEffect(() => {
setMobile(width! <= (breakpoint || 768));
}, [width, breakpoint]);
return isMobile;
};
export default useIsMobile;
const isMobile = useIsMobile(500);
return (
<div className = {styles.nav}>
{!isMobile && <div className = {styles.logo}>Logo</div>}
Решение состоит в том, чтобы приостановить рендеринг элемента до тех пор, пока клиентская сторона не оценит ширину устройства.
'use client';
import { useState, useEffect, useLayoutEffect } from 'react';
import useWindowSize from './useWindowSize';
const useIsMobile = (breakpoint = 768) => {
const { width } = useWindowSize();
const [isMobile, setMobile] = useState(null); // starts as null to indicate no initial assumption
useLayoutEffect(() => {
if (width !== null) {
setMobile(width <= breakpoint);
}
}, [width, breakpoint]);
return isMobile;
};
export default useIsMobile;
Затем в своем компоненте вы можете условно отображать контент в зависимости от того, был ли определен isMobile
:
const isMobile = useIsMobile(500);
if (isMobile === null) {
return <div>Loading...</div>;
}
return (
<div className = {styles.nav}>
{!isMobile && <div className = {styles.logo}>Logo</div>}
</div>
);