【问题标题】:Next js Error "Warning: Expected server HTML to contain a matching <button> in <div>"Next js 错误“警告:预期的服务器 HTML 在 <div> 中包含匹配的 <button>”
【发布时间】:2021-09-05 03:22:36
【问题描述】:

我有一个暗模式组件,它是太阳和月亮图标之间的简单切换。

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 主题时,我将dark 类添加到html

_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 上运行。我克隆了同一个项目。有什么问题?

【问题讨论】:

    标签: javascript reactjs next.js mobx tailwind-css


    【解决方案1】:

    谜题中缺少的部分是我将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>
    

    【讨论】:

    • 这个答案与原始问题无关,但会产生误导。通过将 Nav 移动到 Provider 中,您将无法摆脱 html 不匹配错误
    • @Danila 我知道。但这是通过将其放入内部解决的主要错误。有时会因为其他错误而发生一些错误。
    【解决方案2】:

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

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

    如果你想用 SSR 渲染正确的主题而不闪烁旧主题或没有类似的错误,那么你需要将它存储在 cookie 中。

    【讨论】:

    • 我理解这个问题,我尝试在joshwcomeau.com/react/the-perils-of-rehydration/#the-solution 处遵循解决方案,但我无法让buttononClick 工作。 onClick 的 DOM 完全剥离。如何让buttononClick 出现?你能展示一个示例代码吗?
    • 出现在哪里? “条带”是什么意思?
    • 通过剥离,我的意思是它删除了onClick 处理程序,因此我的button 在Windows 中变为&lt;button class="fixed bottom-12 right-12 focus:outline-none" title="Activate dark mode"&gt;&lt;/button&gt;。我在我的 Mac 上使用了相同的代码(它现在已经永远损坏了)并且它工作正常。我链接的网站有使用hasMounted 钩子的解决方案,我认为这是正确的,但我无法切换任何button,因为DOM 中没有onClick
    • 你用浏览器开发工具检查它吗?那里没有点击处理程序完全没问题
    • 是的,我正在使用开发工具进行检查。该死。直到。但是console.log 也没有出现。
    猜你喜欢
    • 2021-01-15
    • 1970-01-01
    • 2020-07-04
    • 2021-08-06
    • 2021-08-11
    • 1970-01-01
    • 2018-03-08
    • 2021-08-19
    • 2021-08-16
    相关资源
    最近更新 更多