React Testing Library中输入框变更未触发useMemo更新按钮状态问题
React测试中输入框变更后按钮disabled状态未更新的解决方案
我在编写React测试时遇到一个问题:期望输入框内容变更后,useMemo能根据表单状态更新按钮的disabled属性,但使用userEvent操作输入后,按钮状态始终没有变化。相关代码如下:
组件代码
<> <input data-testid="ipt-email" value={form.email} onChange={(e) => { setForm({ ...form, email: e.target.value }); }} /> <button data-testid="submit-sendUser" disabled={isDisabled}> OK </button> </>
useMemo钩子代码
const isDisabled = useMemo(() => { const { email } = form; if (!email.length) return true return false; }, [form]);
测试脚本
import userEvent from "@testing-library/user-event"; it("Should enable button when form is valid", async () => { const wrapper = render(<MyComponent />); const getEmail = wrapper.getByTestId("ipt-email"); await userEvent.type(getEmail, 'example@example.com'); const getBtnSubmit = wrapper.getByTestId("submit-sendUser"); console.log(wrapper.container.innerHTML); expect(getBtnSubmit).not.toBeDisabled(); });
问题原因
核心问题是React状态更新的闭包陷阱:在输入框的onChange回调中,直接使用了当前渲染周期的form变量来创建新状态。当多次触发输入时,这个form可能还是组件初始渲染时的旧值,导致setForm并没有真正更新到最新的表单状态,进而useMemo依赖的form没有发生预期变化,按钮的disabled状态也就不会更新。
解决方案
1. 使用函数式更新状态(根本修复)
将setForm改为函数式更新,这样每次都会接收上一次的最新状态作为参数,避免闭包捕获旧值:
<input data-testid="ipt-email" value={form.email} onChange={(e) => { // 使用prevForm获取最新状态 setForm(prevForm => ({ ...prevForm, email: e.target.value })); }} />
这种写法确保每次状态更新都是基于最新的表单状态,而非渲染周期捕获的旧值,form状态会正确更新,useMemo也会随之重新计算isDisabled。
2. 优化useMemo的依赖(可选)
因为isDisabled的计算只依赖form.email,可以将useMemo的依赖数组改为[form.email],让计算触发更精准,同时提升性能:
const isDisabled = useMemo(() => { return !form.email.length; }, [form.email]);
这一步不是解决测试失败的必需操作,但能让逻辑更清晰,避免不必要的useMemo重新计算。
3. 测试代码的最佳实践(可选)
使用Testing Library推荐的screen对象替代wrapper.getByTestId,让测试代码更简洁符合规范:
import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; it("Should enable button when form is valid", async () => { render(<MyComponent />); const emailInput = screen.getByTestId("ipt-email"); await userEvent.type(emailInput, 'example@example.com'); const submitBtn = screen.getByTestId("submit-sendUser"); expect(submitBtn).not.toBeDisabled(); });
内容的提问来源于stack exchange,提问作者Ruan Duarte
相关产品推荐
相关产品推荐

