使用history.block与异步函数/回调/async/await

5

我有一个表单在一个路由里面,如果存在任何验证错误,它不应该允许用户导航到另一个路由。如果没有验证错误,则允许导航到另一个路由。

以下是我的当前代码,onBlock函数由于其异步性质而无法工作,因为提交和验证表单的函数都是异步的。

FormComponent.js

import React, { useState, useEffect, useRef } from "react";
import { FieldArray } from "formik";
import { useHistory } from "react-router-dom";
import * as Yup from "yup";
import Form from "./Form";
import TextInput from "./TextInput";

const FormComponent = () => {
  const history = useHistory();
  const [initialValues, setInitialValues] = useState();
  const [isSubmitted, setIsSubmitted] = useState(false);
  const block = useRef();

  const formRef = useRef(null);

  const onFormSubmit = async (values) => {
    setIsSubmitted(true);
  };

  const validationSchema = () => {
    const schema = {
      test: Yup.string().required("Input is Required")
    };
    return Yup.object(schema);
  };

  const onBlock = () => {
    const { submitForm, validateForm } = formRef?.current || {};
    // submit form first
    submitForm()
      .then(() => {
        // then validate form
        validateForm()
          .then(() => {
            // if form is valid - should navigate to route
            // if form is not valid - should remain on current route
            return formRef?.current.isValid;
          })
          .catch(() => false);
      })
      .catch(() => false);
  };

  const redirectToPage = () => {
    history.push("/other-page");
  };

  useEffect(() => {
    block.current = history.block(onBlock);

    return () => {
      block.current && block.current();
    };
  });

  useEffect(() => {
    if (isSubmitted) redirectToPage();
  }, [isSubmitted]);

  useEffect(() => {
    setInitialValues({
      test: ""
    });
  }, []);

  return initialValues ? (
    <Form
      initialValues={initialValues}
      onSubmit={onFormSubmit}
      formRef={formRef}
      validationSchema={validationSchema}
    >
      <FieldArray
        name="formDetails"
        render={(arrayHelpers) =>
          arrayHelpers && arrayHelpers.form && arrayHelpers.form.values
            ? (() => {
                const { form } = arrayHelpers;
                formRef.current = form;
                return (
                  <>
                    <TextInput name="test" />
                    <button type="submit">Submit</button>
                  </>
                );
              })()
            : null
        }
      />
    </Form>
  ) : null;
};

export default FormComponent;

如果用户在输入框中没有输入任何值就尝试提交表单,我期望当 onBlock 返回 false 时会阻止导航。但是这似乎不起作用。只有在 onBlock 函数中直接返回 false 才有效。因此看起来 history.block 函数不接受任何回调函数。我还尝试将其转换为 async 函数,并使用 await 等待 submitFormvalidateForm 函数,但仍然不能解决问题。是否有什么方法可以绕过此问题?非常感谢您的帮助。
这里有一个带有示例的CodeSandbox
1个回答

8
history.block 函数接受一个提示回调函数,您可以使用它来提示用户或在页面被阻止时执行其他操作。要阻止页面,只需调用 history.block(),更多信息请参见这里
当你尝试提交formik表单并且成功验证后,该表单将被提交。此时将调用onSubmit回调。因此,如果您想在出现验证错误时阻止页面,则可以使用formik上下文订阅验证isValid,并在其值为false时进行阻止。
const useIsValidBlockedPage = () => {
  const history = useHistory();
  const { isValid } = useFormikContext();

  useEffect(() => {
    const unblock = history.block(({ pathname }) => {
      // if is valid we can allow the navigation
      if (isValid) {
        // we can now unblock
        unblock();
        // proceed with the blocked navigation
        history.push(pathname);
      }
      // prevent navigation
      return false;
    });

    // just in case theres an unmount we can unblock if it exists
    return unblock;
  }, [isValid, history]);
};

这是一个与您的相关代码适应的codesandbox。我删除了一些不需要的组件。
另一个解决方案是在所有页面转换上手动验证,并选择何时允许自己进行转换,在这种情况下,只有当validateForm没有错误时才能进行转换。
// blocks page transitions if the form is not valid
const useFormBlockedPage = () => {
  const history = useHistory();
  const { validateForm } = useFormikContext();

  useEffect(() => {
    const unblock = history.block(({ pathname }) => {
     // check if the form is valid
      validateForm().then((errors) => {
        // if there are no errors this form is valid
        if (Object.keys(errors).length === 0) {
          // Unblock the navigation.
          unblock();
          // retry the pagination
          history.push(pathname);
        }
      });
      // prevent navigation
      return false;
    });
    return unblock;
  }, [history, validateForm]);
};

这里是 codesandbox

,与此相关的it技术。


我最初也有类似的东西,但是 isValid 值最初为 true,因此如果您在不编辑表单的情况下转到另一个路由,它将允许导航,尽管没有填写表单。这就是我想避免的。 - mcclosa
所以你想要防止在表单提交有效内容之前进入另一个路由?如果是这样,只需将 submitCount 添加到逻辑中即可。 - Jonathan Portorreal
如果你正在使用更新版本的 formik,也可以添加 validateOnMount={true} 属性。 - Jonathan Portorreal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接