React表单提交需点击两次,如何让函数读取更新后的state?
问题根源
- 你调用的yup的
validate、isValid都是异步Promise方法,forEach不会等待这些异步操作执行完成,你加的setTimeout(handleInfoCheck)无法保证执行时机是在所有校验完成、state更新之后 - React的状态更新是批量异步的,在同一个事件循环周期内调用的
setIsFormsValid不会立即更新当前作用域下的isFormsValid变量,所以handleInfoCheck读取的永远是本次点击前的旧状态
解决方法
方案1:收集所有校验Promise,等全部完成后直接判断校验结果,不依赖异步更新的state
修改后的代码如下:
const handleValid = async () => { const filteredMembers = members .filter((member: Tourist) => { return member.createdIn === touristCreatedIn && !member.isEmployee; }); // 先处理错误提示的校验 filteredMembers.forEach((member: any, index: any) => { personSchema .validate(member, { abortEarly: false }) .then(() => { setFieldError({ [index]: {} }) }) .catch((errs: any) => { setFieldError({}) errs?.inner?.forEach((err: any) => { setFieldError((prev)=> ({ ...prev, [index]: { ...prev[index], [err.path]: err.message, }, })) }); }); }); // 收集所有校验合法性的Promise const validPromises = filteredMembers.map((member: any) => { return personSchema.isValid(member) .catch((err: any) => { console.error('TouristData YUP isValid Err', err); return false; }); }); // 等待所有校验完成 const validResults = await Promise.all(validPromises); // 直接判断所有结果是否合法,不用读state const allValid = validResults.every(v => v === true); // 先更新状态,再执行跳转逻辑 filteredMembers.forEach((member, index) => { handleSelfValidation(validResults[index], index); }); setFirstVisit(); if (allValid) { switch (permissionType) { case 'tour': history.push(`${addTourUrl}/tour-data`); break; case PERMISSION_TYPE_TRANZIT: history.push(`${addTourUrl}/tranzit-data`); break; default: history.push(`${addTourUrl}/tour-data`); break; } } }; const handleSelfValidation = (isFormValid: boolean, formIndex: number) => { console.log(isFormValid, formIndex, 'test') setIsFormsValid((prev) => ({ ...prev, [formIndex]: isFormValid, })) }; // 不需要在handleValid里调用handleInfoCheck了,逻辑已经整合进去 const handleInfoCheck = () => { setFirstVisit(); if (Object.values(isFormsValid).every((item: any) => item === true)) { switch (permissionType) { case 'tour': history.push(`${addTourUrl}/tour-data`); break; case PERMISSION_TYPE_TRANZIT: history.push(`${addTourUrl}/tranzit-data`); break; default: history.push(`${addTourUrl}/tour-data`); break; } } };
方案2:通过useEffect监听状态变化触发跳转
如果业务需要必须依赖isFormsValid的状态执行后续逻辑,可以用useEffect监听状态更新后再执行跳转:
// 新增useEffect监听 useEffect(() => { if (!firstVisit) return; if (Object.values(isFormsValid).every((item: any) => item === true)) { switch (permissionType) { case 'tour': history.push(`${addTourUrl}/tour-data`); break; case PERMISSION_TYPE_TRANZIT: history.push(`${addTourUrl}/tranzit-data`); break; default: history.push(`${addTourUrl}/tour-data`); break; } } }, [isFormsValid, firstVisit, permissionType, history, addTourUrl]) // handleValid只需要做校验更新状态即可 const handleValid = () => { const filteredMembers = members .filter((member: Tourist) => member.createdIn === touristCreatedIn && !member.isEmployee); filteredMembers.forEach((member: any, index: any) => { personSchema .validate(member, { abortEarly: false }) .then(() => setFieldError({ [index]: {} })) .catch((errs: any) => { setFieldError({}) errs?.inner?.forEach((err: any) => { setFieldError(prev => ({ ...prev, [index]: { ...prev[index], [err.path]: err.message } })) }) }) personSchema.isValid(member) .then(v => handleSelfValidation(v, index)) .catch(err => console.error('TouristData YUP isValid Err', err)) }); setFirstVisit(); };
内容的提问来源于stack exchange,提问作者bazmexes
相关产品推荐
相关产品推荐

