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

如何封装可复用Toast组件实现动态提示?Chakra UI/React技术求助

封装Chakra UI Toast实现表单验证提示

问题背景

开发带输入验证的表单时,需要用Toast展示验证错误信息,但每次重复编写toast({})并指定状态、消息太冗余,想封装一个可接收状态和消息的通用Toast逻辑(基于Chakra UI)。之前尝试的代码报错且Toast不渲染,具体问题如下:

错误代码示例

Abc.jsx

import { Button, useToast } from "@chakra-ui/react"
import Toast from "../Components/Toast"

export default function Abc() {
  const toast = useToast()

  const ToastDisplay = () =>{
    return (
      {Toast}
    )
  }

  return (
    <>
    <Button onClick={()=>{ToastDisplay()}}>
      show toast
    </Button>
    </>
  )
}

Toast.jsx

import { useColorMode } from '@chakra-ui/react';
import { useToast } from "@chakra-ui/react"

export default function Toast() {
    const toast = useToast();

    const optionsForToast = {
        position: "bottom-right",
        autoClose: 300,
        hideProgressBar: false,
        closeOnClick: true,
        pauseOnHover: true,
        draggable: true,
        progress: undefined,
        theme: "dark"
    }
    console.log(optionsForToast)

    return (
        toast({
            title:"hii"
        })
    )
}

报错信息

Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app

错误原因分析

  • 直接在onClick回调中调用Toast()函数,相当于在普通函数内调用useToast Hook,违反了Hook只能在函数组件顶层调用的规则,导致报错。
  • Chakra UI的useToast返回的toast()是触发Toast的函数,调用后返回销毁Toast的方法,并非React元素,不能放在组件的return中作为UI渲染。

解决方案

推荐方案:封装Toast工具函数

这种方式更灵活,符合Toast触发式的使用场景,无需额外状态管理。

  1. 创建工具函数文件utils/toastUtils.js:
import { useToast } from "@chakra-ui/react";

export const useCustomToast = () => {
  const toast = useToast();

  // 通用Toast触发函数,接收状态、消息、自定义标题
  const showToast = (status, message, title) => {
    toast({
      title: title || (status === 'success' ? '操作成功' : '操作失败'),
      description: message,
      status: status, // 可选值:'success' | 'error' | 'warning' | 'info'
      position: "bottom-right",
      duration: 3000,
      isClosable: true,
      // 可添加更多通用配置,比如样式、图标等
    });
  };

  return { showToast };
};
  1. 在表单组件中使用:
import { Button, Input } from "@chakra-ui/react";
import { useCustomToast } from "../utils/toastUtils";
import { useState } from "react";

export default function LoginForm() {
  const [username, setUsername] = useState('');
  const { showToast } = useCustomToast();

  const handleSubmit = () => {
    // 模拟表单验证逻辑
    if (!username.trim()) {
      showToast('error', '用户名不能为空,请重新输入');
      return;
    }

    // 验证通过,提交表单
    showToast('success', '登录成功');
  };

  return (
    <div>
      <Input 
        placeholder="请输入用户名"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
        mb={4}
      />
      <Button onClick={handleSubmit} colorScheme="blue">
        登录
      </Button>
    </div>
  );
}

备选方案:封装受控Toast组件

如果需要通过组件方式控制Toast显示(不推荐,不如工具函数灵活),可以做成受控组件:

  1. 创建components/Toast.jsx:
import { useToast, useEffect } from "@chakra-ui/react";

export default function Toast({ isOpen, status, message, title, onClose }) {
  const toast = useToast();

  useEffect(() => {
    let toastId;
    if (isOpen) {
      toastId = toast({
        title: title || (status === 'success' ? '操作成功' : '操作失败'),
        description: message,
        status,
        position: "bottom-right",
        duration: 3000,
        isClosable: true,
        onCloseComplete: onClose,
      });
    }

    // 组件卸载或状态变化时关闭Toast
    return () => {
      if (toastId) toast.close(toastId);
    };
  }, [isOpen, status, message, title, onClose, toast]);

  return null; // 该组件无需渲染UI元素,仅负责触发Toast
}
  1. 在父组件中使用:
import { Button, Input, useState } from "@chakra-ui/react";
import Toast from "../Components/Toast";

export default function LoginForm() {
  const [username, setUsername] = useState('');
  const [toastConfig, setToastConfig] = useState({
    isOpen: false,
    status: '',
    message: '',
  });

  const handleSubmit = () => {
    if (!username.trim()) {
      setToastConfig({
        isOpen: true,
        status: 'error',
        message: '用户名不能为空,请重新输入',
      });
      return;
    }

    setToastConfig({
      isOpen: true,
      status: 'success',
      message: '登录成功',
    });
  };

  const handleToastClose = () => {
    setToastConfig(prev => ({ ...prev, isOpen: false }));
  };

  return (
    <div>
      <Input 
        placeholder="请输入用户名"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
        mb={4}
      />
      <Button onClick={handleSubmit} colorScheme="blue">
        登录
      </Button>
      <Toast 
        isOpen={toastConfig.isOpen}
        status={toastConfig.status}
        message={toastConfig.message}
        onClose={handleToastClose}
      />
    </div>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 19:29:44