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

React Context+Hooks状态无法填充及测试报错问题求助

React 18 Context 初始化与测试问题

问题背景

我编写了一段React 18的Context Provider代码,用于异步加载配置并更新Context,但测试时遇到两个核心问题:

  • Context中的internalConfig始终是空对象{},无论在Provider还是Consumer中都无法获取到正确的配置值
  • 测试过程中出现组件更新冲突警告,以及错误边界提示

Provider 核心代码

import { Dispatch, ReactNode, SetStateAction, Suspense, createContext, useEffect, useState } from "react";
import { InternalConfig } from "../types/config";
import config from "../src/config";
import React from "react";

export const EtechUIContext = createContext<[InternalConfig, Dispatch<SetStateAction<InternalConfig>>] | []>([]);

export default function EtechUIProvider({
  children,
  configPath,
  options
}: {
  children: ReactNode;
  configPath: string;
  options?: {
    filter: string;
  };
}) {
  const [internalConfig, setInternalConfig] = useState<InternalConfig>({} as InternalConfig);
  const [calledTimes, setCalledTimes] = useState(0);

  useEffect(() => {
    const init = async (configPath: string, options?: { filter: string }) => {
      try {
        const newConfig = await config().init(configPath, options);
        setInternalConfig(newConfig);
        setCalledTimes(0);
      } catch (err: any) {
        setCalledTimes((prev) => prev++);
        if (calledTimes >= 3) {
          throw new Error("Failed to initialize eTech UI. This is likely a bug, please report it :)");
        }
      }
    };

    if (Object.keys(internalConfig).length === 0) {
      init(configPath, options);
    }
  }, [internalConfig, calledTimes]);

  return (
    <Suspense fallback={<p>Loading...</p>}>
      <EtechUIContext.Provider value={[internalConfig, setInternalConfig]}>{children}.</EtechUIContext.Provider>
    </Suspense>
  );
}

测试时的错误信息

The above error occurred in the <TestConsumer> component:

    at TestConsumer (/Users/ekrich/git/etech-ui/packages/etech-ui-utils/contexts/tests/EtechUI.spec.tsx:17:58)
    at Suspense
    at EtechUIProvider (/Users/ekrich/git/etech-ui/packages/etech-ui-utils/contexts/EtechUI.tsx:10:3)

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.

Warning: Cannot update a component (`EtechUIProvider`) while rendering a different component (`TestConsumer`). To locate the bad setState() call inside `TestConsumer`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
    at TestConsumer (/Users/ekrich/git/etech-ui/packages/etech-ui-utils/contexts/tests/EtechUI.spec.tsx:26:73)
    at Suspense
    at EtechUIProvider (/Users/ekrich/git/etech-ui/packages/etech-ui-utils/contexts/EtechUI.tsx:10:3)

注:实际未提供上述错误的具体详情

我已经尝试按照提示使用Suspense组件,但问题仍未解决。

测试代码(Vitest + React Testing Library)

import EtechUiProvider, { EtechUIContext } from "../EtechUI";
import { describe, it, expect } from "vitest";
import { render } from "@testing-library/react";
import React, { useContext } from "react";

describe("EtechUIContext", () => {
  it("should accept children into the provider", () => {
    render(
      <EtechUiProvider configPath="./etech.config.ts" options={{ filter: "etech-ui-utils-tests" }}>
        Testing
      </EtechUiProvider>
    );
  });

  it("should contain an InternalConfig in context", () => {
    function TestConsumer() {
      expect(useContext(EtechUIContext)[0]?.configName).toBe("etech-ui-utils-tests");
      return null;
    }

    render(
      <EtechUiProvider configPath="./etech.config.ts" options={{ filter: "etech-ui-utils-tests" }}>
        <TestConsumer />
      </EtechUiProvider>
    );
  });

  it("should have a setInternalConfig function that updates the InternalConfig", () => {
    function TestConsumer() {
      const [internalConfig, setInternalConfig] = useContext(EtechUIContext);
      console.log("internal", internalConfig);
      expect(setInternalConfig).toBeDefined();

      // @ts-ignore
      // Ignore error: cannot invoke null object.
      // The above expect statement ensures that setInternalConfig is defined
      setInternalConfig({
        name: "etech-ui-utils",
        configName: "etech-ui-utils-tests",
        uiDir: "packages/etech-ui-utils/ui",
        theme: {
          colors: {
            primary: "red",
            secondary: "purple",
            success: "green",
            warning: "orange",
            error: "red"
          },
          colorMode: "browser"
        }
      });

      expect(internalConfig?.theme.colors.primary).toBe("red");
      return null;
    }

    render(
      <EtechUiProvider configPath="./etech.config.ts" options={{ filter: "etech-ui-utils-tests" }}>
        <TestConsumer />
      </EtechUiProvider>
    );
  });
});

目前只有should accept children into the provider这个测试用例能够通过。

尝试的修改(使用act回调)

我尝试用act包裹测试逻辑,但Context中的InternalConfig仍然是空对象{}:

import EtechUiProvider, { EtechUIContext } from "../EtechUI";
import { describe, it, expect } from "vitest";
import { act, render, renderHook } from "@testing-library/react";
import React, { Context, useContext, useEffect } from "react";

const usePopulated = (context: Context<any[]>, expected: string) => {
  const [internalConfig] = useContext(context);
  if (internalConfig.configName === expected) {
    return true;
  }

  return false;
};

describe("EtechUIContext", () => {
  it("should accept children into the provider", () => {
    render(
      <EtechUiProvider configPath="./etech.config.ts" options={{ filter: "etech-ui-utils-tests" }}>
        Testing
      </EtechUiProvider>
    );
  });

  it("should have a setInternalConfig function that updates the InternalConfig", async () => {
    function TestConsumer() {
      const [internalConfig, setInternalConfig] = useContext(EtechUIContext);
      useEffect(() => {
        if (setInternalConfig) {
          // @ts-ignore
          setInternalConfig({
            name: "etech-ui-utils",
            configName: "etech-ui-utils-tests",
            uiDir: "packages/etech-ui-utils/ui",
            theme: {
              colors: {
                primary: "red",
                secondary: "purple",
                success: "green",
                warning: "orange",
                error: "red"
              },
              colorMode: "browser"
            }
          });
        }
      }, []);

      return null;
    }

    act(() => {
      const { result } = renderHook(() => usePopulated(EtechUIContext, "etech-ui-utils-tests"), {
        wrapper: ({ children }) => (
          <EtechUiProvider configPath="./etech.config.ts" options={{ filter: "etech-ui-utils-tests" }}>
            {children}
          </EtechUiProvider>
        )
      });

      expect(result.current).toBe(true);
    });
  });
});

特此求助解决上述问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 04:07:02