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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 11:20:39