如何在 useEffect 挂钩中形成 setFieldValue

Posted

技术标签:

【中文标题】如何在 useEffect 挂钩中形成 setFieldValue【英文标题】:How to Formik setFieldValue in useEffect hook 【发布时间】:2020-06-07 01:47:51 【问题描述】:

我有一个 Formik 表单,它需要根据通过路由器传递的信息动态更改。我需要运行 graphQL 查询来检索一些数据并使用检索到的数据填充表单。我能够设置表单并检索数据,但我不知道如何在 useEffect 挂钩中为基础表单设置字段值。我觉得我错过了一些重要的部分来访问 Formik 上下文,但我无法从文档中找到它。

任何帮助都会很棒。

import React,  useState, useEffect  from "react";
import Router,  useRouter  from "next/router";

import Container from "react-bootstrap/Container";
import  Field, Form, FormikProps, Formik  from "formik";
import * as Yup from "yup";

import  useLazyQuery  from "@apollo/react-hooks";
import  GET_PLATFORM  from "../graphql/platforms";

export default function platformsForm(props) 
  const router = useRouter();

  // grab the action requested by caller and the item to be updated (if applicable)
  const [formAction, setFormAction] = useState(router.query.action);
  const [formUpdateId, setFormUpdateId] = useState(router.query.id);

  const [initialValues, setInitialValues] = useState(
    platformName: "",
    platformCategory: ""
  );

  const validSchema = Yup.object(
    platformName: Yup.string().required("Name is required"),
    platformCategory: Yup.string().required("Category is required")
  );

  const [
    getPlatformQuery,
     loading, error, data: dataGet, refetch, called 
  ] = useLazyQuery(GET_PLATFORM, 
    variables:  id: formUpdateId 
  );

  useEffect(() => 
    !called && getPlatformQuery( variables:  id: formUpdateId  );
    if (dataGet && dataGet.Platform.platformName) 
      console.log(
        dataGet.Platform.platformName,
        dataGet.Platform.platformCategory
      );

      //
      // vvv How do I set Field values at this point if I don't have Formik context
      // setFieldValue();
      //
    
  ),
    [];

  const onSubmit = async (values,  setSubmitting, resetForm ) => 
    console.log("submitted");
    resetForm();
    setSubmitting(false);
  ;

  return (
    <Container>
      <Formik
        initialValues=initialValues
        validationSchema=validSchema
        onSubmit=onSubmit
      >
        (
          handleSubmit,
          handleChange,
          handleBlur,
          handleReset,
          values,
          touched,
          isInvalid,
          isSubmitting,
          isValidating,
          submitCount,
          errors
        ) => (
          <Form>
            <label htmlFor="platformName">Name</label>
            <Field name="platformName" type="text" />

            <label htmlFor="platformCategory">Category</label>
            <Field name="platformCategory" type="text" />

            <button type="submit">Submit</button>
          </Form>
        )
      </Formik>
    </Container>
  );


【问题讨论】:

【参考方案1】:

我想我想通了,但不确定。我发现一些地方提到了一个 Formik innerRef 道具,所以尝试了一下,它似乎有效。我在文档或教程中都没有提到它,所以我不确定这是否是一些不受支持的功能,或者可能只是应该用于内部 Formik 的东西,但它似乎对我有用所以我将使用它,直到找到更好的方法。我已经花了更长的时间来分享我想要分享的内容。 :|

欢迎提出意见或建议。或者,如果您认为这是正确的方法,也可以投票。

为了解决这个问题,我在函数主体中添加了一个 useRef:

  const formikRef = useRef();

然后我将其添加为道具:

      <Formik
        innerRef=formikRef
        initialValues=initialValues
        validationSchema=validSchema
        onSubmit=onSubmit
      >

一旦我这样做了,我就可以从 useEffect 中引用 Formik 函数,所以在我的例子中,我做了以下事情:

      if (formikRef.current) 
        formikRef.current.setFieldValue(
          "platformName",
          dataGet.Platform.platformName
        );
        formikRef.current.setFieldValue(
          "platformCategory",
          dataGet.Platform.platformCategory
        );
      

【讨论】:

我已经尝试过你的方法,并注意到当 formik 道具发生变化时,formikRef 并不总是得到更新。虽然我还没有为我的具体案例找到正确的解决方案,但 Daniel Schmidt 作为一篇关于此的文章 - Ref objects inside useEffect Hooks 我认为所有这些助手都应该通过 useFormik() 钩子返回【参考方案2】:

访问 Formik 状态和助手的正确方法是使用 Formik 的 useFormikContext 钩子。这将为您提供所需的一切。

查看文档以获取详细信息和示例: https://formik.org/docs/api/useFormikContext

【讨论】:

【参考方案3】:

几天前我遇到了类似的问题,我想重用一个表单来创建和更新事件。更新时,我从数据库中获取已经存在的事件的数据,并用相应的值填充每个字段。

通过将箭头函数更改为这样的命名函数,我能够在 formik 中使用 useEffect 和 setFieldValue

    <Formik
  initialValues=initialValues
  validationSchema=validationSchema
  onSubmit=handleSubmit
>
  function ShowForm(
    values,
    errors,
    touched,
    handleChange,
    handleSubmit,
    handleBlur,
    setFieldValue,
    isValid,
    dirty,
  ) 
    useEffect(() => 
      if (!isCreateMode) 
        axios
          .get(`/event/$id`)
          .then((response) => 
            const data = response.data.data;
            const fields = [
              "title",
              "description",
              "startDate",
              "endDate",
              "time",
              "venue",
              "link",
              "banner",
            ];
            fields.forEach((field) => 
              setFieldValue(field, data[field], false);
            );
          )
          .catch((error) => console.log("error:", error));
      
    , [setFieldValue]);
    return (
      <form action="" onSubmit=handleSubmit className="event-form">
        <Grid container justify="center">
          <Grid item lg=12 style= textAlign: "center" >
            <h2>isCreateMode ? "Create New Event" : "Edit Event"</h2>
          </Grid>
          <Grid item lg=6>
            <TextField
              name="title"
              id="title"
              label="Event title"
              value=values.title
              type="text"
              onBlur=handleBlur
              error=touched.title && Boolean(errors.title)
              helperText=touched.title ? errors.title : null
              variant="outlined"
              placeholder="Enter event title"
              onChange=handleChange
              fullWidth
              margin="normal"
            />
            <Button
              variant="contained"
              disableElevation
              fullWidth
              type="submit"
              disabled=!(isValid && dirty)
              className="event-submit-btn"
            >
              Publish Event
            </Button>
          </Grid>
        </Grid>
      </form>
    );
  
</Formik>;

回到你的用例,你可以简单地将你的表单重构为这个

return (
  <Container>
    <Formik
      initialValues=initialValues
      validationSchema=validSchema
      onSubmit=onSubmit
    >
      function myForm(
        handleSubmit,
        handleChange,
        handleBlur,
        handleReset,
        values,
        touched,
        isInvalid,
        isSubmitting,
        isValidating,
        submitCount,
        errors,
      ) 
        useEffect(() => 
          !called && getPlatformQuery( variables:  id: formUpdateId  );
          if (dataGet && dataGet.Platform.platformName) 
            console.log(
              dataGet.Platform.platformName,
              dataGet.Platform.platformCategory
            );

            /* run setFieldValue here */
          
        , []);
        return (
          <Form>
            <label htmlFor="platformName">Name</label>
            <Field name="platformName" type="text" />

            <label htmlFor="platformCategory">Category</label>
            <Field name="platformCategory" type="text" />

            <button type="submit">Submit</button>
          </Form>
        );
      
    </Formik>
  </Container>
);

【讨论】:

【参考方案4】:

解决这个问题的一个技巧是在 Formik 表单中设置一个不可见的按钮。此按钮的onClick 将可以访问与Formik 相关的所有内容,例如setFieldValuesetTouched 等。然后您可以使用document.getElementById('..').click()useEffect“模拟”单击此按钮。这将允许您从 useEffect 执行 Formik 操作。

例如

// Style it to be invisible
<Button id="testButton" type="button" onClick=() => 
    setFieldValue('test', '123');
    setTouched();    
    // etc. any Formik action
>
</Button> 

使用效果:

useEffect(() => 
    document.getElementById("testButton").click(); // Simulate click
, [someVar);

【讨论】:

【参考方案5】:

刚刚处理这个问题一段时间,发现以下解决方案利用嵌套组件内的 useFormikContext。

// this is a component nested within a Formik Form, so it has FormikContext higher up in the dependency tree

const [field, _meta, helpers] = useField(props);
const  setFieldValue  = useFormikContext();
const [dynamicValue, setDynamicValue] = useState('testvalue')

const values = ['value1', 'value2', dynamicValue];

const [selectedIndex, setSelectedIndex] = useState(field.value);

// have some update operation on setting dynamicValue

//handle selection 
const handleSelect = (index) =>  
setSelectedIndex(index);
 helpers.setField(values[index])




//handle the update
  useEffect(() => 
    setCustomTheme(dynamicValue);
    setFieldValue("<insertField>", dynamicValue);
  , [dynamicValue, setFieldValue]);

这需要一个父 Formik 元素,以便它可以正确利用 useField 和 useFormikContext。它似乎可以正确更新并且正在为我们运行。

【讨论】:

以上是关于如何在 useEffect 挂钩中形成 setFieldValue的主要内容,如果未能解决你的问题,请参考以下文章

如何在 React 中使用 useEffect 挂钩调用多个不同的 api

在 useEffect 挂钩中使用 axios 取消令牌时如何修复失败的测试

无法在 useEffect 挂钩中测试超时功能

如何在数组依赖中正确使用 useEffect 挂钩。我从 redux 商店传递了状态,但我的组件仍然无限渲染

如何从 Reactjs 中的 useEffect 挂钩访问道具

如何使用 jest/enzyme 测试 useEffect 与 useDispatch 挂钩?