我已经创建了这个codesandbox示例,下面是代码:
import React, { ReactNode, useState } from "react";
import { Formik, FormikConfig, FormikProps, Form, FormikErrors } from "formik";
import { useEffect } from "react";
import { scrollToValidationError } from "./scrollToValidationError";
// const isEmpty = (a: unknown): boolean =>
// typeof a === "object" && Object.keys(a).length > 0;
export type FormContainerProps<V> = {
render({
values,
errors,
invalid,
submitCount,
isSubmitting
}: {
values: V;
invalid: boolean;
errors: FormikErrors<V>;
submitCount: number;
isSubmitting: boolean;
}): ReactNode;
additionalContent?: ReactNode;
nextButtonText?: string;
} & Pick<FormikConfig<V>, "initialValues" | "validate"> &
Partial<Pick<FormikConfig<V>, "onSubmit">>;
export const FormContainer = function FormContainer<V>({
initialValues,
additionalContent,
validate,
render,
...rest
}: FormContainerProps<V>) {
const [hasValidationError, setHasValidationError] = useState(false);
useEffect(() => {
if (!hasValidationError) {
return;
}
scrollToValidationError();
}, [hasValidationError]);
return (
<>
<Formik
initialValues={initialValues}
validate={validate}
onSubmit={async (values, { validateForm }) => {}}
>
{({
isSubmitting,
submitCount,
isValid,
errors,
values
}: FormikProps<V>) => {
const invalid = !isValid;
if (submitCount > 0 && invalid) {
setHasValidationError(true);
}
return (
<>
<div data-selector="validation-summary">Validation Summary</div>
<Form>
<div>
<div>
{render({
values,
errors,
isSubmitting,
invalid,
submitCount
})}
</div>
<div>
<button type="submit">SUBMIT</button>
</div>
</div>
</Form>
</>
);
}}
</Formik>
</>
);
};
基本上,我正在呼叫setHasValidationError(true)
,这打破了useEffect上的依赖项监视程序
useEffect(() => {
if (!hasValidationError) {
return;
}
scrollToValidationError();
setTimeout(() => setHasValidationError(false));
}, [hasValidationError]);
但是,如果这是一个有多个错误的表单,那么我想每次触发useEffect,但是我不知道何时将其重置为false或是否有更好的方法。
答案 0 :(得分:2)
要在单击“提交”后滚动到第一个错误字段,可以执行以下操作:
automatic scroll
到元素和错误输入时的focus
isSubmitting
和errors
来处理滚动和聚焦逻辑FocuseabelField自定义组件
const FocuseabelField: any = props => {
const elementRef = useRef<HTMLDivElement>();
if (
props.isSubmitting &&
elementRef.current !== undefined &&
props.errors.hasOwnProperty(props.name)
) {
elementRef.current.scrollIntoView();
elementRef.current.focus();
}
return <Field {...props} innerRef={elementRef} />;
};
用法
<FocuseabelField
errors={errors}
isSubmitting={isSubmitting}
name="name"
placeholder="enter name"
className={errors && errors.name ? "input error" : "input"}
/>
我接受了您的代码,并注释了诸如scrollToValidationerror.ts,dom.ts,wait.ts,useState(hasValidationError),useEffect等之类的东西。
此处是代码的简化工作副本。我使用2个字段来演示多个错误,并自动滚动并聚焦到错误字段:
https://codesandbox.io/s/usemachine-typescript-problems-tns0c?file=/src/components/Home/index.tsx
当表单变大时,管理变得复杂,因此考虑将表单验证部分外包并使用诸如yup之类的库并维护验证模式并将其传递给formik很好。
看看formik docs为例。
答案 1 :(得分:1)
如何为每个表单字段创建带有键的对象?这样,您可以为每个输入维护一个特定的表单验证错误,并在useEffect第二个参数中使用该对象,这将确保它在每次表单错误更新时被触发
答案 2 :(得分:0)
要回答您的原始问题,
useEffect
会参考检查其依赖关系,因此您可以使用Object
代替值。像这样。
const [hasValidationError, setHasValidationError] = useState({value: false});
useEffect(() => {
if (!hasValidationError.value) {
return;
}
scrollToValidationError();
}, [hasValidationError]);
setHasValidationError({value: true});
但是关于Formik的使用,我强烈建议您遵循@gdh指出的内容。
答案 3 :(得分:0)
我不明白这里是否需要效果。
为什么不直接使用方法而不使用钩子呢?
这样做可以避免两次重新渲染,并且组件也可以是无状态的!
...
}: FormikProps<V>) => {
const invalid = !isValid;
if (submitCount > 0 && invalid) {
scrollToValidationError();
}
...