如何在useFormik中传递表单值并调用自定义useSubmit Hook
正确实现Formik与自定义Hook useSubmit的整合方案
你的核心问题是违反了React Hook调用规则——Hook必须在组件顶层作用域调用,不能嵌套在onSubmit这类回调函数内部。正确的实现方式是在组件顶层调用useSubmit Hook,再在Formik的onSubmit中调用Hook返回的submit方法即可。
修改后的完整组件代码
import { useFormik } from "formik"; import { useEffect } from "react"; import { Box, Button, FormControl, FormErrorMessage, FormLabel, Heading, Input, Select, Textarea, VStack, } from "@chakra-ui/react"; import * as Yup from 'yup'; import FullScreenSection from "./FullScreenSection"; import useSubmit from "../hooks/useSubmit"; import {useAlertContext} from "../context/alertContext"; const LandingSection = () => { const { isLoading, response, submit } = useSubmit(); const { onOpen } = useAlertContext(); // 监听提交结果,触发提示弹窗 useEffect(() => { if (response) { onOpen(response.type, response.message); } }, [response, onOpen]); const formik = useFormik({ initialValues: { firstName:"", email:"", type:"", comment:"" }, onSubmit: async (values) => { // 调用useSubmit返回的submit方法,传入目标地址和表单值 await submit("your-api-endpoint", values); }, validationSchema: Yup.object({ firstName: Yup.string().required('Required'), email: Yup.string().email('Invalid email address').required('Required'), type: Yup.string().required('Please pick an option'), comment: Yup.string().required('write a comment') }), }); return ( <FullScreenSection isDarkBackground backgroundColor="#512DA8" py={16} spacing={8} > <VStack w="1024px" p={32} alignItems="flex-start"> <Heading as="h1" id="contactme-section"> Contact me </Heading> <Box p={6} rounded="md" w="100%"> <form onSubmit={formik.handleSubmit}> <VStack spacing={4}> {/* 姓名输入框 */} <FormControl isInvalid={formik.touched.firstName && formik.errors.firstName}> <FormLabel htmlFor="firstName">Name</FormLabel> <Input id="firstName" name="firstName" onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.firstName} /> <FormErrorMessage>{formik.errors.firstName}</FormErrorMessage> </FormControl> {/* 邮箱输入框 */} <FormControl isInvalid={formik.touched.email && formik.errors.email}> <FormLabel htmlFor="email">Email Address</FormLabel> <Input id="email" name="email" type="email" onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.email} /> <FormErrorMessage>{formik.errors.email}</FormErrorMessage> </FormControl> {/* 咨询类型选择框 */} <FormControl isInvalid={formik.touched.type && formik.errors.type}> <FormLabel htmlFor="type">Type of enquiry</FormLabel> <Select id="type" name="type" onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.type} > <option value="">Select option</option> <option value="hireMe">Freelance project proposal</option> <option value="openSource">Open source consultation</option> <option value="other">Other</option> </Select> <FormErrorMessage>{formik.errors.type}</FormErrorMessage> </FormControl> {/* 留言输入框 */} <FormControl isInvalid={formik.touched.comment && formik.errors.comment}> <FormLabel htmlFor="comment">Your message</FormLabel> <Textarea id="comment" name="comment" height={200} onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.comment} /> <FormErrorMessage>{formik.errors.comment}</FormErrorMessage> </FormControl> {/* 提交按钮(绑定加载状态) */} <Button type="submit" colorScheme="purple" width="full" isLoading={isLoading} loadingText="Submitting" > Submit </Button> </VStack> </form> </Box> </VStack> </FullScreenSection> ); }; export default LandingSection;
关键修改说明
- 顶层调用useSubmit:在组件最外层调用
useSubmit,获取submit方法、isLoading和response状态,完全符合Hook规则。 - Formik onSubmit调用submit方法:在Formik的
onSubmit回调中,直接调用submit方法并传入实际API地址和表单值,替换代码中的your-api-endpoint即可。 - 监听提交结果触发提示:通过
useEffect监听response变化,提交完成后自动调用onOpen显示成功/失败提示。 - 加载状态处理:给提交按钮绑定
isLoading状态,禁用按钮并显示加载文本,避免用户重复提交。
可选优化:添加response重置方法
如果需要避免组件重渲染时重复触发提示,可以修改useSubmit Hook添加重置方法:
// 修改后的useSubmit Hook import {useState} from "react"; const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const useSubmit = () => { const [isLoading, setLoading] = useState(false); const [response, setResponse] = useState(null); const submit = async (url, data) => { const random = Math.random(); setLoading(true); try { await wait(2000); if (random < 0.5) { throw new Error("Something went wrong"); } setResponse({ type: 'success', message: `Thanks for your submission ${data.firstName}, we will get back to you shortly!`, }) } catch (error) { setResponse({ type: 'error', message: 'Something went wrong, please try again later!', }) } finally { setLoading(false); } }; // 添加重置response的方法 const resetResponse = () => { setResponse(null); }; return { isLoading, response, submit, resetResponse }; } export default useSubmit;
然后在组件的useEffect中调用重置方法:
useEffect(() => { if (response) { onOpen(response.type, response.message); resetResponse(); } }, [response, onOpen, resetResponse]);
内容的提问来源于stack exchange,提问作者Nicholas Poh
相关产品推荐
相关产品推荐

