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

使用MUI Popover时出现React act警告的问题求助

解决React测试中TextField+Popover的act警告问题

测试包含TextField和Popover的React组件时,始终无法避免React的act警告。该组件由父组件控制Popover的显示时机与内容,相关代码及警告如下:

组件代码

const PopoverContainer = (props: TextFieldWithPopoverProps) => {
    const [anchorEl, setAnchorEl] = React.useState(null);
    const anchorRef = React.useRef(null);

    React.useEffect(() => {
        setAnchorEl(anchorRef.current);
    }, [anchorRef]);

    return (
        <>
            <TextField type="text" ref={anchorRef}/>
            <Popover id={props.popperId} open={props.open} sx={{ zIndex: 1001 }} anchorEl={anchorEl}>
                <Paper>
                    {props.renderedChild}
                </Paper>
            </Popover>
        </>
    );
};

测试代码

test('should open popover', async () => {
    const { rerender } = render(<PopoverContainer open={false} popperId="popper-id" renderedChild={renderInnerPopover()} />);

    expect(screen.queryByText('Hello World!')).toBe(null);

    await userEvent.type(await screen.findByRole('textbox'), 'HELLO');

    rerender(<PopoverContainer open={true} popperId="popper-id" renderedChild={renderInnerPopover()} />);

    expect(await screen.findByText('Hello World!'));
});

const renderInnerPopover = () => {
    return (
        <div>
            Hello World!
        </div>
    );
};

出现的警告

Warning: An update to ForwardRef(FormControl) inside a test was not wrapped in act(...)

Warning: An update to ForwardRef(Popover) inside a test was not wrapped in act(...)

警告始终出现在rerender调用时,尝试用act()包裹rerender和type事件无效,移除TextField则警告消失,但不符合组件设计需求。


有效解决方法

1. 移除不必要的状态转换,直接使用ref.current

原组件中通过useEffect将ref.current存入state是多余操作,会触发额外组件更新,这是导致act警告的核心原因之一。直接将ref.current传给Popover的anchorEl即可:

const PopoverContainer = (props: TextFieldWithPopoverProps) => {
    const anchorRef = React.useRef(null);

    return (
        <>
            <TextField type="text" ref={anchorRef}/>
            <Popover id={props.popperId} open={props.open} sx={{ zIndex: 1001 }} anchorEl={anchorRef.current}>
                <Paper>
                    {props.renderedChild}
                </Paper>
            </Popover>
        </>
    );
};

2. 优化测试代码,确保所有更新被正确等待

使用waitFor包裹断言,确保等待Popover内部所有异步更新完成,避免测试在组件未完全渲染时执行断言:

import { waitFor } from '@testing-library/react';

test('should open popover', async () => {
    const { rerender } = render(<PopoverContainer open={false} popperId="popper-id" renderedChild={renderInnerPopover()} />);

    expect(screen.queryByText('Hello World!')).toBeNull();

    // 组件渲染后textbox已存在,无需await
    await userEvent.type(screen.getByRole('textbox'), 'HELLO');

    rerender(<PopoverContainer open={true} popperId="popper-id" renderedChild={renderInnerPopover()} />);

    // 等待Popover完全渲染完成
    await waitFor(() => {
        expect(screen.getByText('Hello World!')).toBeInTheDocument();
    });
});

const renderInnerPopover = () => {
    return (
        <div>
            Hello World!
        </div>
    );
};

3. 禁用测试环境中的组件动画

Material-UI的Popover、TextField等组件内部可能包含动画逻辑,这些异步动画会触发额外状态更新,导致act警告。在测试全局配置中禁用动画:

// setupTests.ts
import '@testing-library/jest-dom';
import { disableAnimations } from '@testing-library/react';

beforeAll(() => {
    disableAnimations();
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 09:21:35