dev*_*_el 15 javascript local-storage reactjs next.js
我正在使用下一个13.1.0。我有一个 ContextProvider 设置浅色和深色主题
'use client';
import { Theme, ThemeContext } from '@store/theme';
import { ReactNode, useState, useEffect } from 'react';
interface ContextProviderProps {
children: ReactNode
}
const ContextProvider = ({ children }: ContextProviderProps) => {
const [theme, setTheme] = useState<Theme>('dark');
useEffect(() => {
const storedTheme = localStorage.getItem('theme');
if (storedTheme === 'light' || storedTheme === 'dark') {
setTheme(storedTheme);
} else {
localStorage.setItem('theme', theme);
}
// added to body because of overscroll-behavior
document.body.classList.add(theme);
return () => {
document.body.classList.remove(theme);
};
}, [theme]);
const toggle = () => {
const newTheme = theme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
localStorage.setItem('theme', newTheme);
};
return (
<ThemeContext.Provider value={{ theme, toggle }}>
{children}
</ThemeContext.Provider>
);
};
export { ContextProvider };
Run Code Online (Sandbox Code Playgroud)
我在根布局中使用它
import '@styles/globals.scss';
import { GlobalContent } from '@components/GlobalContent/GlobalContent';
import { ContextProvider } from '@components/ContextProvider/ContextProvider';
import { Inter } from '@next/font/google';
import { ReactNode } from 'react';
const inter = Inter({ subsets: ['latin'] });
interface RootLayoutProps {
children: ReactNode
}
const RootLayout = ({ children }: RootLayoutProps) => {
return (
<html lang="en" className={inter.className}>
<head />
<body>
<ContextProvider>
<GlobalContent>
{children}
</GlobalContent>
</ContextProvider>
</body>
</html>
);
};
export default RootLayout;
Run Code Online (Sandbox Code Playgroud)
我在 GlobalContent 中使用了主题值
'use client';
import styles from '@components/GlobalContent/GlobalContent.module.scss';
import { GlobalHeader } from '@components/GlobalHeader/GlobalHeader';
import { GlobalFooter } from '@components/GlobalFooter/GlobalFooter';
import { ThemeContext } from '@store/theme';
import { ReactNode, useContext } from 'react';
interface GlobalContentProps {
children: ReactNode
}
const GlobalContent = ({ children }: GlobalContentProps) => {
const { theme } = useContext(ThemeContext);
return (
<div className={`${theme === 'light' ? styles.lightTheme : styles.darkTheme}`}>
<GlobalHeader />
<div className={styles.globalWrapper}>
<main className={styles.childrenWrapper}>
{children}
</main>
<GlobalFooter />
</div>
</div>
);
};
export { GlobalContent };
Run Code Online (Sandbox Code Playgroud)
我收到错误
Hydration failed because the initial UI does not match what was rendered on the server.
Run Code Online (Sandbox Code Playgroud)
我不明白为什么会收到此错误,因为我正在访问localStorage
内部useEffect
,所以我希望服务器上生成的 HTML 在第一次渲染之前与客户端相同。
我该如何解决这个错误?
Jer*_*yal 15
对于 Next.js 13,一旦组件被调用,就返回 jsxmounted
function Component() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return <></>;
// write rest of your code
}
Run Code Online (Sandbox Code Playgroud)
我已经制定了一个解决方法,暂时解决了这个问题,但代价是放弃 SSR。
通过在 my 上使用动态导入ContextProvider
,我禁用服务器渲染并且错误消失了。作为奖励,从我的默认深色主题到保存的浅色主题的闪烁问题localStorage
消失了。但我放弃了SSR的好处。如果有人找到更好的解决方案,请分享。
import '@styles/globals.scss';
import { GlobalContent } from '@components/GlobalContent/GlobalContent';
import { Inter } from '@next/font/google';
import dynamic from 'next/dynamic';
import { ReactNode } from 'react';
const inter = Inter({ subsets: ['latin'] });
interface RootLayoutProps {
children: ReactNode
}
// Fixes: Hydration failed because the initial UI does not match what was rendered on the server.
const DynamicContextProvider = dynamic(() => import('@components/ContextProvider/ContextProvider').then(mod => mod.ContextProvider), {
ssr: false
});
const RootLayout = ({ children }: RootLayoutProps) => {
return (
<html lang="en" className={inter.className}>
<head />
<body>
<DynamicContextProvider>
<GlobalContent>
{children}
</GlobalContent>
</DynamicContextProvider>
</body>
</html>
);
};
export default RootLayout;
Run Code Online (Sandbox Code Playgroud)
此解决方案不会在站点范围内禁用 SSR。我使用以下代码添加了一个新的测试页面
async function getData() {
const res = await fetch('https://rickandmortyapi.com/api/character', { cache: 'no-store' });
if (!res.ok) {
throw new Error('Failed to fetch data');
}
return res.json();
}
export default async function Page() {
const data = await getData();
return (
<main>
{data.results.map((c: any) => {
return (
<p key={c.id}>{c.name}</p>
);
})}
</main>
);
}
Run Code Online (Sandbox Code Playgroud)
运行后npm run build
可以看到测试页面使用的是ssr
在检查测试页面的响应时,我可以看到 HTML 响应
当我将此 Navbar 组件添加到 Next.js 13 中的 layout.tsx 中的 RootLayout 中时,遇到了类似的问题。
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<Navbar />
<body className={font.className}>
{children}
</body>
</html>
);
}
Run Code Online (Sandbox Code Playgroud)
我更改了上面的代码,而是在 body 标记内添加了导航栏,它解决了问题。
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={font.className}>
<Navbar />
{children}
</body>
</html>
);
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
28107 次 |
最近记录: |