将 history.block 与异步 functions/callback/async/await 结合使用

Using history.block with asynchronous functions/callback/async/await

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

下面是我当前的代码,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 会 return false 阻止导航。但这似乎不起作用。然而,只需在 onBlock 函数中 returning false 即可。所以看起来 history.block 函数不接受任何回调。我也尝试将其转换为 async 函数和 await submitFormvalidateForm 函数,但仍然没有任何乐趣。有没有解决的办法?任何帮助将不胜感激。

这里有一个例子CodeSandbox

history.block 函数接受提示回调,您可以使用它来提示用户或执行其他操作以响应页面被阻止。要阻止该页面,您只需调用 history.block() 更多信息 here.

当您尝试提交 formik 表单时会对其进行验证,如果验证成功,则会继续提交表单,这时将调用 onSubmit 回调。因此,如果您想在出现验证错误时阻止该页面,您可以使用 formik 上下文来订阅验证 isValid 并在出现错误时阻止。

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 returns 没有错误。

// 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