如何封装可复用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:
- You might have mismatching versions of React and the renderer (such as React DOM)
- You might be breaking the Rules of Hooks
- You might have more than one copy of React in the same app
错误原因分析
- 直接在
onClick回调中调用Toast()函数,相当于在普通函数内调用useToastHook,违反了Hook只能在函数组件顶层调用的规则,导致报错。 - Chakra UI的
useToast返回的toast()是触发Toast的函数,调用后返回销毁Toast的方法,并非React元素,不能放在组件的return中作为UI渲染。
解决方案
推荐方案:封装Toast工具函数
这种方式更灵活,符合Toast触发式的使用场景,无需额外状态管理。
- 创建工具函数文件
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 }; };
- 在表单组件中使用:
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显示(不推荐,不如工具函数灵活),可以做成受控组件:
- 创建
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 }
- 在父组件中使用:
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
相关产品推荐
相关产品推荐

