尝试使用 Formik 重构 onSubmit 属性

Trying to refactor the onSubmit property using Formik

用 React 提高我的开发技能。我正在尝试想办法重构 onSubmit 属性。我的应用程序是一个使用 Formik 组件的联系表单,该组件将数据发送到 Firebase Cloudstore 并通过 emailjs 发送电子邮件。如果成功,它将有一个使用 Material UI 的 Snackbar 的弹出窗口。它有效,但只是试图清理代码。请帮忙!

onSubmit={(values, { resetForm, setSubmitting }) => {          
          emailjs.send("blah","blah", {
            email: values.email,
            name: values.name,
            message: values.message
            }, 
            'blah',);    

          //this is sent to firebase cloudstore
          db.collection("contactForm")
            .add({
              name: values.name,
              email: values.email,
              message: values.message,
            })
            .then(() => {
              handleClick();
            })
            .catch((error) => {
              alert(error.message);
            });
          setTimeout(() => {
            resetForm();
            setSubmitting(false);
            /*    console.log(values);
            console.log(JSON.stringify(values, null, 2)); */
          }, 500);
        }}

这是完整的函数

function Contact() {
  const [open, setOpen] = React.useState(false);
  const handleClose = (event, reason) => {
    if (reason === "clickaway") {
      return;
    }
    setOpen(false);
  };
  const handleClick = () => {
    setOpen(true);
  };
  const classes = useStyles();
  return (
      <Formik
        initialValues={initialValues}
        validationSchema={validationSchema}
        onSubmit={(values, { resetForm, setSubmitting }) => {          
          emailjs.send("blah","blah", {
            email: values.email,
            name: values.name,
            message: values.message
            }, 
            'blah',);    

          //this is sent to firebase cloudstore
          db.collection("contactForm")
            .add({
              name: values.name,
              email: values.email,
              message: values.message,
            })
            .then(() => {
              handleClick();
            })
            .catch((error) => {
              alert(error.message);
            });
          setTimeout(() => {
            resetForm();
            setSubmitting(false);
            /*    console.log(values);
            console.log(JSON.stringify(values, null, 2)); */
          }, 500);
        }}
      >
        {({ submitForm, isSubmitting }) => (
          <Form>
            <Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
              <Alert onClose={handleClose} severity="success">
                Your message has been sent!
              </Alert>
            </Snackbar>

            <div>
              <Field
                component={TextField}
                label="Name"
                name="name"
                type="name"
              />
              <ErrorMessage name="name" />
            </div>

            <div>
              <Field
                component={TextField}
                label="Your email"
                name="email"
                type="email"
              />
              <ErrorMessage name="email" />
            </div>
            <br />
            <br />
            <div>
              <Field
                as="textarea"
                placeholder="Your Message"
                label="message"
                name="message"
                type="message"
                rows="15"
                cols="70"                
              />
              <ErrorMessage name="message" />
            </div>

            {isSubmitting && <LinearProgress />}

            <Button
              variant="contained"
              color="primary"
              disabled={isSubmitting}
              onClick={submitForm}
            >
              Submit
            </Button>
          </Form>
        )}
      </Formik>
  );
}

我建议让 onSubmit 属性 在组件主体中成为它自己的函数,您需要使用 useCallback 记住它。此外,您可以创建一个挂钩以允许您控制警报组件,您还可以允许挂钩控制天气是错误还是成功类型,从而减少保存失败时重复代码的需要。

您的提交处理程序可能如下所示,请注意,我省略了电子邮件的发送并模拟了 firebase 部分。您也可以在 promise 上调用 finally,而不是在 thencatch 块中调用 setSubmitting。

  const handleSubmit = React.useCallback(
    (values, { setSubmitting, resetForm }) => {
      db.collection("contact")
        .add(values)
        .then((res) => {
          show({ message: "Your message has been sent" });
        })
        .catch((err) => {
          show({ variant: "error", message: "Failed to send your message." });
        })
        .finally(() => {
          setSubmitting(false);
        });
    },
    [show]
  );

上面示例中的 show 函数将成为控制警报的钩子的一部分。钩子可能看起来像这样,它可以根据您的用例进行扩展。

import React from "react";

const useAlert = () => {
  const [state, setState] = React.useState({
    variant: "success",
    visibile: false,
    message: null
  });

  const show = React.useCallback(
    (options = {}) => {
      setState((prev) => ({
        ...prev,
        ...options,
        visible: true
      }));
    },
    [setState]
  );

  const hide = React.useCallback(() => {
    setState((prev) => ({ ...prev, visibile: false }));
  }, [setState]);

  return { ...state, show, hide };
};

export default useAlert;

此外,由于您正在使用 material ui,因此您需要在组件中利用它们的 built。这将消除对多个 <br /> 间距的需要,并有助于保持 UI 一致。

<Box marginBottom={1}>
  <Field component={TextField} label="Name" name="name" type="name" />
  <ErrorMessage name="name" />
</Box>

<Box marginBottom={1}>
  <Field
    component={TextField}
    label="Email"
    name="email"
    type="email"
  />
  <ErrorMessage name="email" />
</Box>

此外,您可以将 built in 组件用于文本区域,以保持设计的一致性。使用 multiline prop 可以使输入成为文本区域。

<Box marginBottom={2}>
  <Field
    component={TextField}
    placeholder="Your Message"
    label="Message"
    name="message"
    type="message"
    rows={5}
    multiline
    fullWidth
  />
  <ErrorMessage name="message" />
</Box>

我个人不太喜欢以您的方式使用 LinearProgress。我个人认为循环过程看起来更好,特别是在提交按钮内部使用时。 Here are the relevant docs.

<Button
  variant="contained"
  color="primary"
  disabled={isSubmitting}
  onClick={submitForm}
  endIcon={isSubmitting && <CircularProgress size={15} />}
>
  Submit
</Button>

I've put a working example together in a codesandbox.