带有打字稿的 formik 在 withFormik HOC 中设置了初始值

formik with typescript set initalvalue in withFormik HOC

我正在使用 typescript 添加 formik 并对我的项目做出反应,所以我将 withFormik 钩子用作 HOC,我面临的问题是我无法为响应设置初始值(在 hoc 中定义)我我来自 api

login.tsx

import { FormikProps } from "formik";
export interface FormValues {
  title: string;
}

export const Login: React.FC<FormikProps<FormValues>> = (props) => {
  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/todos/1")
      .then((response) => response.json())
      .then((json) => setTitle(json.title));
  }, []);
  const [title, setTitle] = useState<string | null>(null);


  const { handleSubmit, handleBlur, handleChange, touched, errors, values } =
    props;

  return (
    <Container component="main" maxWidth="xs">

      <div className={classes.paper}>


        <form className={classes.form} noValidate onSubmit={handleSubmit}>
          <TextField
            variant="outlined"
            margin="normal"
            required
            fullWidth
            id="title"
            label="Title"
            name="title"
            value={values.title}
            autoFocus
            autoComplete="off"
            onChange={handleChange}
            onBlur={handleBlur}
          />

         

          <Button
            type="submit"
            fullWidth
            variant="contained"
            color="primary"
            className={classes.submit}
          >
            Sign In
          </Button>
        </form>
      </div>
    </Container>
  );
};

login.hoc.tsx

import { Form, FormikProps, FormikValues, useFormik, withFormik } from "formik";


import { FormValues, Login } from "./index";
interface MyFormikProps {

}

export const LoginView = withFormik<MyFormikProps, FormValues>({
  enableReinitialize: true,
  mapPropsToValues: (props) => {
    return { title:"" };
  },

  handleSubmit: (values) => {
    console.log(values);
  },
})(Login);

这工作得很好,但我的问题是假设我在 login.tsx 的 didmount 中点击了一个 api 然后你可以看到我设置了“标题”来响应我从api

现在我想将“标题”的初始值设置为我从 api

得到的响应

为什么你的 HOC 不起作用

您当前的设置可能无法工作,因为层次结构不正确。 withFormik HOC 从组件的 props 创建初始值。但是 API 响应永远不会出现在 Loginprops 中。它被保存到 Login 组件的 inside 状态。 外部 的 HOC 无法访问它。

您可以设置 withFormikprops 中获取正确的 API 数据,但您需要将 API 的提取提升到更高级别.

// component which renders the form, no more API fetch
export const Login: React.FC<FormikProps<FormValues>> = (props) => {/*...*/}

// data passed down from API
interface MyFormikProps {
  title: string | null;
}

// component which sets up the form
export const LoginView = withFormik<MyFormikProps, FormValues>({
  enableReinitialize: true,
  mapPropsToValues: (props) => ({
    title: props.title ?? "" // fallback if null
  }),
  handleSubmit: (values) => {
    console.log(values);
  }
})(Login);

// component which loads the data
export const ApiLoginForm = () => {
  const [title, setTitle] = useState<string | null>(null);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/todos/1")
      .then((response) => response.json())
      .then((json) => setTitle(json.title));
  }, []);

  return <LoginView title={title} />;
};

Code Sandbox Link

处理异步初始值

您 API 的价值需要时间来解决。将它用作 initial 值是很棘手的,因为一旦安装了窗体,初始值就必须存在。我们在这里所做的是回退到使用空字符串 '' 作为初始值。然后一旦 API 响应加载,表单将重置为新的初始值,因为您使用了 enableReinitialize setting.

这将重置整个表单,如果 API 响应缓慢,可能会导致糟糕的用户体验。如果用户在响应返回之前在表单中输入文本,您不会希望覆盖用户输入。您可以禁用该字段,直到响应可用。或者您可以通过检查 touched.title 是否为 truevalues.title 来使修改成为有条件的。如果您想更深入地了解如何修改表单,请继续阅读...

以编程方式修改 Formik 值

您可以使用 FormikProps 中的 setFieldValue prop 强制修改字段的任何值。您可以通过调用 setFieldValue.

来更改当前值,而不是更改初始值

这种方法更符合您当前的设置,因为它允许您将 API 调用保留在 Login 内。我们不需要本地 title 状态。您可以调用 setFieldValue.

而不是使用已解析的响应调用 setTitle
export const Login: React.FC<FormikProps<FormValues>> = (props) => {

  // access setFieldValue from props
  const { setFieldValue, handleSubmit, handleBlur, handleChange, touched, errors, values } =
    props;

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/todos/1")
      .then((response) => response.json())
      // could make this conditional, or add extra logic here
      .then((json) => setFieldValue("title", json.title));
  }, []);

  return (
    /* same JSX as before */
  )
};

Code Sandbox Link