You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Context异常:useContext获取默认值而非传入值的排查修复

问题:ThemeContext子组件获取到默认值而非Provider传入值

我创建了一个允许子组件调用setTheme的ThemeContext,但在子组件中通过useContext导入使用时,返回的是默认值而非Provider传入的值。请问我哪里出错了?该如何修复?这是什么原因导致的?

相关代码

_document.tsx

type ThemeContextType = [string, React.Dispatch<React.SetStateAction<string>>]

export const ThemeContext: React.Context<ThemeContextType> = createContext([
    "light",
    (value: string | ((prevState: string) => string)): void => {
        console.log("default")
    },
])

/** create a context for theme and set theme */
export default function Document(): JSX.Element {
    const [theme, setTheme] = useState("light")

    return (
        <Html lang="vi">
            <Head>
                <meta charSet="utf-8" />
                <meta
                    name="viewport"
                    content="width=device-width, initial-scale=1"
                />
                <meta
                    name="theme-color"
                    content="#000000"
                />
                <link
                    rel="manifest"
                    href="/manifest.json"
                />
                <link
                    rel="shortcut icon"
                    href="/favicon.ico"
                />
            </Head>

            <body data-theme={theme}>
                <ThemeContext.Provider value={[theme, setTheme]}>
                    <Main />
                </ThemeContext.Provider>
                <NextScript />
            </body>
        </Html>
    )
}

students-info.tsx

export default function StudentsInfo({
    capturing,
    setCapturing,
}: {
    capturing: boolean
    setCapturing: React.Dispatch<React.SetStateAction<boolean>>
}): JSX.Element {
    const [theme, setTheme] = useContext(ThemeContext)
    const data: Student[] = useMemo((): Student[] => students, [])
    const table: Table<Student> = useReactTable({
        data,
        columns,
        getCoreRowModel: getCoreRowModel(),
    })

    useRenderEffect((): void => {
        if (!capturing) return

        const dom: HTMLElement | null = document.getElementById("students")
        if (!dom) return

        /** current theme */
        const theme_: string = theme
        setTheme("light")

        /** fit dom into jspdf and save */
        const pdf: jsPDF = new jsPDF("p", "pt", "a4")

        pdf.html(dom, {
            callback: (pdf: jsPDF): void => {
                pdf.save("students.pdf")

                setTheme(theme_)

                setCapturing(false)
            },
        })
    }, [capturing])

    return (
        <table id="students">
            <TableHead table={table} />
            <TableBody table={table} />
        </table>
    )
}

问题原因

你错误地将ThemeContext.Provider放在了Next.js的_document.tsx中。_document.tsx是Next.js用于自定义服务端HTML渲染结构的特殊组件,它不属于客户端React应用的组件树层级。客户端组件(比如StudentsInfo)在进行hydration和日常渲染时,无法访问到这个Provider传递的上下文值,因此会回退使用createContext时传入的默认值。

修复方案

1. 单独封装ThemeContext

创建一个独立的上下文文件(比如contexts/ThemeContext.tsx),将上下文定义和Provider逻辑分离:

// contexts/ThemeContext.tsx
import { createContext, useState, useContext, ReactNode } from 'react';

type ThemeContextType = [string, React.Dispatch<React.SetStateAction<string>>];

// 定义上下文
const ThemeContext = createContext<ThemeContextType>([
  "light",
  () => console.log("default"),
]);

// 封装Provider组件
export function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState("light");
  return (
    <ThemeContext.Provider value={[theme, setTheme]}>
      {children}
    </ThemeContext.Provider>
  );
}

// 自定义钩子,简化上下文调用
export const useTheme = () => useContext(ThemeContext);

2. 清理_document.tsx

移除_document.tsx中所有ThemeContext相关代码,只保留HTML结构:

// _document.tsx
import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    <Html lang="vi">
      <Head>
        <meta charSet="utf-8" />
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1"
        />
        <meta
          name="theme-color"
          content="#000000"
        />
        <link
          rel="manifest"
          href="/manifest.json"
        />
        <link
          rel="shortcut icon"
          href="/favicon.ico"
        />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

3. 在_app.tsx中挂载Provider

Next.js的_app.tsx是客户端应用的根组件,所有页面都会被它包裹,在这里挂载Provider才能确保所有客户端组件都能访问上下文:

// _app.tsx
import { ThemeProvider } from '@/contexts/ThemeContext';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <ThemeProvider>
      <Component {...pageProps} />
    </ThemeProvider>
  );
}

4. 修改子组件调用方式

在students-info.tsx中使用自定义的useTheme钩子获取上下文:

// students-info.tsx
import { useTheme } from '@/contexts/ThemeContext';

export default function StudentsInfo({
  capturing,
  setCapturing,
}: {
  capturing: boolean;
  setCapturing: React.Dispatch<React.SetStateAction<boolean>>;
}) {
  const [theme, setTheme] = useTheme();
  // 剩余代码保持不变
}

内容的提问来源于stack exchange,提问作者Fukumi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 20:20:20