微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Next js 错误“警告:预期服务器 HTML 在 <div> 中包含匹配的 <button>”

如何解决Next js 错误“警告:预期服务器 HTML 在 <div> 中包含匹配的 <button>”

我有一个黑暗模式组件,它可以在太阳和月亮图标之间进行简单的切换。

DarkMode.tsx

import { observer } from 'mobx-react'
import { MoonIcon,SunIcon } from '@heroicons/react/solid'

import { useStore } from '@/store/index'

export const DarkMode = observer(() => {
    const { theme,setTheme,isPersisting } = useStore()

    if (!isPersisting) return null

    return (
        <>
            {theme === 'dark' && (
                <button
                    className="fixed bottom-12 right-12 focus:outline-none"
                    title="Activate light mode"
                    onClick={() => {
                        setTheme('light')
                    }}
                >
                    <MoonIcon className="w-8 h-8" />
                </button>
            )}
            {theme === 'light' && (
                <button
                    className="fixed bottom-12 right-12 focus:outline-none"
                    title="Activate dark mode"
                    onClick={() => {
                        setTheme('dark')
                    }}
                >
                    <SunIcon className="w-8 h-8" />
                </button>
            )}
        </>
    )
})

我正在使用 MobX 跟踪我的 thememobx-persist-store 以将数据保存在 localStorage 中。

store.ts

import { makeObservable,observable,action } from 'mobx'
import { makePersistable,isPersisting,clearPersistedStore } from 'mobx-persist-store'

import type { Theme,IStore } from '@/types/index'

const name = 'Store'
const IS_SERVER = typeof window === 'undefined'

export class Store implements IStore {
    theme: Theme = 'light'

    constructor() {
        makeObservable(this,{
            theme: observable,setTheme: action.bound,reset: action.bound,})

        if (!IS_SERVER) {
            makePersistable(this,{ name,properties: ['theme'],storage: window.localStorage })
        }
    }

    setTheme(theme: Theme) {
        this.theme = theme
    }

    get isPersisting() {
        return isPersisting(this)
    }

    async reset() {
        if (!IS_SERVER) await clearPersistedStore(this)
    }
}

用户在暗模式组件中选择 dark 主题时,我将 html添加dark

_app.tsx

import React from 'react'
import { AppProps } from 'next/app'
import Head from 'next/head'
import { observer } from 'mobx-react'
import useSystemTheme from 'use-system-theme'

import { useStore } from '@/store/index'

import '@/components/NProgress'

import 'nprogress/nprogress.css'
import '@/styles/index.css'

const MyApp = ({ Component,pageProps }: AppProps) => {
    const systemTheme = useSystemTheme()
    const { theme,setTheme } = useStore()

    React.useEffect(() => {
        const isDarkTheme = theme === 'dark' || (systemTheme === 'dark' && theme !== 'light')
        if (isDarkTheme) {
            document.documentElement.classList.add('dark')
            setTheme('dark')
        } else {
            document.documentElement.classList.remove('dark')
            setTheme('light')
        }
    },[theme,systemTheme])

    return (
        <>
            <Component {...pageProps} />
        </>
    )
}

export default observer(MyApp)

我仍然收到一条错误消息:

VM356 main.js:16820 Warning: Expected server HTML to contain a matching <button> in <div>.
    at button
    at wrappedComponent (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1624277701361:2690:73)
    at Nav (http://localhost:3000/_next/static/chunks/pages/tutorial/the-complete-guide-to-starting-a-blog-in-nextjs-and-mdx.js?ts=1624277701361:12454:23)
    at Tutorial (http://localhost:3000/_next/static/chunks/pages/tutorial/the-complete-guide-to-starting-a-blog-in-nextjs-and-mdx.js?ts=1624277701361:12973:24)
    at MDXLayout
    at http://localhost:3000/_next/static/chunks/pages/tutorial/the-complete-guide-to-starting-a-blog-in-nextjs-and-mdx.js?ts=1624277701361:7880:30
    at Mdxcontent (http://localhost:3000/_next/static/chunks/pages/tutorial/the-complete-guide-to-starting-a-blog-in-nextjs-and-mdx.js?ts=1624277701361:22563:25)
    at wrappedComponent (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1624277701361:2690:73)
    at ErrorBoundary (http://localhost:3000/_next/static/chunks/main.js?ts=1624277701361:767:47)
    at ReactDevOverlay (http://localhost:3000/_next/static/chunks/main.js?ts=1624277701361:883:23)
    at Container (http://localhost:3000/_next/static/chunks/main.js?ts=1624277701361:8756:5)
    at AppContainer (http://localhost:3000/_next/static/chunks/main.js?ts=1624277701361:9244:24)
    at Root (http://localhost:3000/_next/static/chunks/main.js?ts=1624277701361:9380:25)

buttononClick 事件处理程序从 DOM 本身消失。

有趣的是它过去可以在 MacOS 上运行,但不能在 Windows 上运行。我克隆了同一个项目。有什么问题?

解决方法

在服务器上,您的 DarkMode 组件不会呈现任何内容(因为 isPersisting 为 false)。然后在客户端,它在第一次通过时呈现一些东西(isPersisting 在客户端呈现时变为真),这就是 React(不是 Next.js)抱怨 SSR 和 CSR 之间的标记不匹配的原因。

基本上这意味着你总是需要用 SSR 渲染一些主题,但 SSR 不知道 localStorage 所以它只能选择默认值。然后在客户端渲染后从 localStorage 中选择正确的值。

如果您想使用 SSR 呈现正确的主题,而不会闪现旧主题或没有类似错误,那么您需要将其存储在 cookie 中。

,

拼图的缺失部分是我将 Nav 包裹在 ThemeProvider 外面。

Nav 包含 DarkMode,因此无法访问 ThemeProvider。我的 _document.tsx 看起来像:

<Nav />
<ThemeProvider attribute="class" themes={['light','dark']}>
    <Component {...pageProps} />
</ThemeProvider>

所以我必须将 Nav 放入 ThemeProvider 中才能使其正常工作。

<ThemeProvider attribute="class" themes={['light','dark']}>
    <Nav />
    <Component {...pageProps} />
</ThemeProvider>

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。