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

如何在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;

关键修改说明

  1. 顶层调用useSubmit:在组件最外层调用useSubmit,获取submit方法、isLoading和response状态,完全符合Hook规则。
  2. Formik onSubmit调用submit方法:在Formik的onSubmit回调中,直接调用submit方法并传入实际API地址和表单值,替换代码中的your-api-endpoint即可。
  3. 监听提交结果触发提示:通过useEffect监听response变化,提交完成后自动调用onOpen显示成功/失败提示。
  4. 加载状态处理:给提交按钮绑定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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 21:07:22