我想异步验证用户输入。例如,检查电子邮件是否已存在,并在用户键入时执行验证。为了减少 API 调用,我想使用 lodash 或自定义去抖动函数来去抖动 API 调用,并在用户停止输入时执行验证。
到目前为止,这是我的表格。问题是它没有按预期工作。看来被谴责的函数返回了上一次调用的值,我不明白问题出在哪里。
您可以在这里看到一个实例:https://codesandbox.io/s/still-wave-qwww6
import { isEmailExists } from "./api";
const debouncedApi = _.debounce(isEmailExists, 300, {
trailing: true
});
export default function App() {
const validationSchema = yup.object({
email: yup
.string()
.required()
.email()
.test("unique_email", "Email must be unique", async (email, values) => {
const response = await debouncedApi(email);
console.log(response);
return response;
})
});
const formik = useFormik({
initialValues: {
email: ""
},
validateOnMount: true,
validationSchema: validationSchema,
onSubmit: async (values, actions) => {}
});
return (
<form onSubmit={formik.handleSubmit}>
<label>
Email:
<input
type="text"
name="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
<div className="error-message">{formik.errors.email}</div>
</label>
</form>
);
}
我使用以下函数模拟 API 调用:
export const isEmailExists = async email => {
return new Promise(resolve => {
console.log('api call', email);
setTimeout(() => {
if (email !== '[email protected]') {
return resolve(true);
} else {
return resolve(false);
}
}, 200);
})
}
更新: 尝试编写我自己的去抖动功能的实现。这样,最后一个 Promise 的解析将被保留到超时,然后函数才会被调用,Promise 才会被解析。
const debounce = func => {
let timeout;
let previouseResolve;
return function(query) {
return new Promise(async resolve => {
//invoke resolve from previous call and keep current resolve
if (previouseResolve) {
const response = await func.apply(null, [query]);
previouseResolve(response);
}
previouseResolve = resolve;
//extending timeout
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
timeout = setTimeout(async () => {
const response = await func.apply(null, [query]);
console.log('timeout expired', response);
previouseResolve(response);
timeout = null;
}, 200);
})
}
}
const debouncedApi = debounce(isEmailExists);
const validationSchema = yup.object({
email: yup
.string()
.required()
.email()
.test('unique_email', 'Email must be unique', async (email, values) => {
const response = await debouncedApi(email);
console.log('test response', response);
return response;
})
});
不幸的是,它也不起作用。看起来是的,当下一次调用发生时,会中止未解析的函数调用。当我打字快时,它不起作用,当我打字慢时,它起作用。 您可以在此处查看更新的示例:https://codesandbox.io/s/suspicious-chaum-0psyp
看起来被谴责的函数返回了先前调用的值
这就是 lodash debounce 的工作原理:
对去抖函数的后续调用将返回最后一次 func 调用的结果。
参见:https://lodash.com/docs/4.17.15#debounce
您可以将
validateOnChange
设置为 false
,然后手动调用 formik.validateForm
,作为副作用:
import debounce from 'lodash/debounce';
import { isEmailExists } from "./api";
const validationSchema = yup.object({
email: yup
.string()
.required()
.email()
.test("unique_email", "Email must be unique", async (email, values) => {
const response = await isEmailExists(email);
console.log(response);
return response;
})
});
export default function App() {
const formik = useFormik({
initialValues: {
email: ""
},
validateOnMount: true,
validationSchema: validationSchema,
validateOnChange: false, // <--
onSubmit: async (values, actions) => {}
});
const debouncedValidate = useMemo(
() => debounce(formik.validateForm, 500),
[formik.validateForm],
);
useEffect(
() => {
console.log('calling deboucedValidate');
debouncedValidate(formik.values);
},
[formik.values, debouncedValidate],
);
return (
...
);
}
这样,整个验证将被反跳,而不仅仅是远程调用。
如果没有依赖关系,最好将模式放在组件之外,在每次渲染中执行此操作通常很慢。
如果您不希望所有验证都被反跳(仅字段的异步验证),实现此目的的另一种方法是利用自定义更改处理程序和
setFieldError
。
如果您想在验证时阻止提交,可以使用
setStatus
。
import { isEmailExists } from "./api";
import { debounce } from 'lodash';
const debouncedEmailValidation = debounce((val, setFieldError) => {
isEmailExists(val)
.then(res => {
if (res.data.exists) {
setFieldError('email', 'Email already exists');
}
});
}, 300, {
trailing: true
});
const validationSchema = yup.object({
email: yup
.string()
.required()
.email()
});
export default function App() {
return (
<Formik
initialValues={{
email: ""
}}
validationSchema={validationSchema}
onSubmit={console.log}
>
{({ values, errors, handleBlur, handleChange, setFieldError }) => {
const handleEmailChange = (e) => {
handleChange(e);
debouncedEmailValidation(e.target.value, setFieldError);
};
return (
<Form>
<label>
Email:
<input
type="text"
name="email"
onChange={handleEmailChange}
onBlur={handleBlur}
value={values.email}
/>
<div className="error-message">{errors.email}</div>
</label>
</Form>
)
}}
</Formik>
);
}
如果你想使用 < Formik > 组件(像我一样),你可以像这样取消验证(感谢之前的回答,它帮助我做到这一点):
import { Formik, Form, Field } from "formik"
import * as Yup from 'yup';
import { useRef, useEffect, useMemo } from 'react'
import debounce from 'lodash.debounce'
const SignupSchema = Yup.object().shape({
courseTitle: Yup.string().min(3, 'Too Short!').max(200, 'Too Long!').required('Required'),
courseDesc: Yup.string().min(3, 'Too Short!').required('Required'),
address: Yup.string().min(3, 'Too Short!').max(200, 'Too Long!').required('Required'),
});
export default function App() {
const formik = useRef() // <------
const debouncedValidate = useMemo(
() => debounce(() => formik.current?.validateForm, 500),
[formik],
);
useEffect(() => {
console.log('calling deboucedValidate');
debouncedValidate(formik.current?.values);
}, [formik.current?.values, debouncedValidate]);
return (
<Formik
innerRef={formik} // <------
initialValues={{
courseTitle: '',
courseDesc: '',
address: '',
}}
validationSchema={SignupSchema}
validateOnMount={true} // <------
validateOnChange={false} // <------
...
在我的例子中,带有表单的组件不会重新渲染,并且这个 useEffect 不起作用。 有人知道为什么会这样吗?字段值更新。 我已经完成了最后一条评论。
export const CreditForm: React.FC = () => {
const [selectData, setSelectData] = useState(initialSelectorsData);
const [successfulSubmit, setSuccessfulSubmit] = useState(false);
useEffect(() => {
const getCreditFormData = async () => {
const data = await creditFormApi.getDateForCreditForm();
setSelectData(data);
};
getCreditFormData();
}, []);
// const formik = useRef<HTMLFormElement | null>(null);
const formik = useRef(null);
const debouncedValidate = useMemo(
() => debounce(() => formik.current?.validateForm, 500),
[formik]
);
useEffect(() => {
console.log('calling deboucedValidate');
debouncedValidate(formik.current?.values);
}, [formik.current?.values, debouncedValidate]);
console.log(formik.current?.values);
console.log({ formik });
return (
<div className="flex items-center">
{!successfulSubmit ? (
<Formik
initialValues={initialFormData}
validationSchema={validationSchema}
validateOnBlur={false}
validateOnChange={false}
innerRef={formik}
onSubmit={async values => {
try {
await creditFormApi.submitForm(values);
setSuccessfulSubmit(true);
} catch (error) {
alert('Something went wrong');
}
}}
>
{({
values,
setFieldValue,
isSubmitting,
isValid,
isValidating,
}: FormikProps<IInitialValues>) => (
<Form className="flex flex-col md:flex-row w-full rounded-lg overflow-hidden">
<div className={cn(formColumnStyles, 'md:py-32 bg-[#fff]')}>
<h2 className="text-2xl md:text-3xl lg:text-4xl font-extrabold text-center mb-3">
Submit External Credit
</h2>
<p className="text-center mb-3 font-medium">Enter your credit details below</p>
<InputField
name="email"
placeholder="Email"
checkIcon={true}
isValidating={isValidating}
asyncValidating={true}
/>
</div>
</Form>
)}
</Formik>
) : (
<SuccessfulSubmit />
)}
</div>
);
};