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

React useState布尔值不更新:自定义Toast二次触发不显示求助

问题根源分析
  1. 状态不同步:Toast组件内部维护的showed状态,在用户点击关闭按钮后与父组件传入的isShowed props脱节——关闭时修改了内部showed为false,但父组件的toast.isShowed仍保持true。
  2. useEffect未触发:第二次触发错误时,父组件setToast的isShowed值还是true(未重置),导致Toast组件的isShowed props没有变化,依赖它的useEffect不会执行,内部showed保持为false,Toast因此不显示。
  3. 关闭逻辑错误:关闭按钮点击事件中,错误使用props的isShowed来切换内部状态,而非基于内部showed状态或通知父组件更新状态,导致逻辑混乱。
修复方案

1. 将Toast改为受控组件(推荐)

移除Toast内部的showed状态,直接使用父组件传入的isShowed props,通过回调通知父组件处理关闭操作,让状态统一由父组件管理。

修改后的Toast组件:

export type ToastProps = {
  color: 'error' | 'success' | 'warning';
  message: string;
  isShowed: boolean;
  onClose: () => void; // 添加关闭回调
};

const Toast = (props: ToastProps) => {
  const { color, message, isShowed, onClose } = props;

  return (
    <div
      className="toast-notification-wrapper"
      style={{
        position: 'fixed',
        top: '20px',
        left: '50%',
        transform: 'translateX(-50%)',
        zIndex: 9999,
        padding: '15px',
        borderRadius: '5px',
        transition: 'all 0.3s ease-out',
        opacity: isShowed ? 1 : 0,
        pointerEvents: isShowed ? 'auto' : 'none',
        display: 'flex',
        justifyContent: 'space-between',
        minWidth: '150px',
        backgroundColor:
          color === 'error'
            ? '#dc3545'
            : color === 'success'
            ? '#28a745'
            : '#ffc107',
      }}
    >
      <p style={{ margin: 0, color: 'white', fontSize: '15px', lineHeight: 1 }}>
        {message}
      </p>
      <span
        style={{
          cursor: 'pointer',
          display: 'flex',
          alignContent: 'center',
          justifyContent: 'center',
          marginLeft: '30px',
        }}
        onClick={onClose} // 调用父组件的关闭回调
      >
        <FontAwesomeIcon
          icon={faClose}
          style={{ color: 'white', fontSize: '15px' }}
        />
      </span>
    </div>
  );
};

2. 更新Login组件的状态管理

在Login组件中实现Toast的关闭回调,确保每次触发错误时正确设置Toast状态:

修改后的Login组件相关部分:

const Login = (props: { isCompleted: boolean; isLoggedIn: boolean }) => {
  // ... 其他原有状态

  // 处理Toast关闭
  const handleToastClose = () => {
    setToast(prev => ({ ...prev, isShowed: false }));
  };

  const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();

    try {
      await login({ username, password });
      window.location.href = '/';
    } catch (error: any) {
      // 直接设置isShowed为true,确保Toast触发显示
      setToast({
        isShowed: true,
        color: 'error',
        message:
          error.response.data?.message ||
          'Unexpected Error. Please try again later',
      });
    }
  };

  return (
    <div className="login-form">
      <Toast
        isShowed={toast.isShowed}
        color={toast.color}
        message={toast.message}
        onClose={handleToastClose} // 传递关闭回调
      ></Toast>
      {/* ... 其他原有表单内容 */}
    </div>
  );
};

3. 可选:添加自动关闭功能(优化体验)

如果需要Toast自动关闭,可在Toast组件中添加定时器:

const Toast = (props: ToastProps) => {
  const { color, message, isShowed, onClose } = props;

  useEffect(() => {
    let timer: NodeJS.Timeout;
    if (isShowed) {
      // 3秒后自动关闭
      timer = setTimeout(() => {
        onClose();
      }, 3000);
    }
    return () => clearTimeout(timer);
  }, [isShowed, onClose]);

  // ... 其余渲染代码
};
修复逻辑说明
  • 受控组件模式让状态统一由父组件管理,彻底避免子组件内部状态与props的不一致问题。
  • 关闭回调确保父组件同步更新状态,下次触发错误时isShowed从false变为true,Toast能正常触发显示。
  • 移除子组件内部的showed状态,减少状态维护复杂度,避免不必要的同步逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 13:15:06