React Formik表单onChange无回调失效及setState错误问题咨询
问题解决:Formik表单中避免bad setState错误并实时控制按钮禁用
问题描述
现有代码触发了bad setState错误,需求是在表单字段未填写完整时禁用按钮:
- 希望
onChange中的代码无需使用()=>回调就能立即执行 - 使用箭头函数回调时,
onChange的状态更新总是滞后一次输入变化
错误代码片段:
<FormControl onChange={ Object.keys(formik.errors).length === 0 && Object.keys(formik.touched).length >= 2 ? setErrorState(false, formik.values) : setErrorState(true) } >
问题原因
- 直接执行函数而非传递回调:当前代码中
onChange的值是表达式的执行结果(直接调用了setErrorState),这会在组件渲染时就触发setState,违反React渲染期间不能更新状态的规则,导致bad setState错误。 - 状态滞后问题:使用箭头函数时,若依赖的
formik.errors/formik.touched是闭包捕获的旧值,会因为Formik状态更新时机导致回调拿到的不是最新状态,出现滞后。
解决方案
方案1:利用Formik内置状态监听,用useEffect实时同步
不需要在FormControl上绑定onChange,而是通过useEffect监听Formik的核心状态变化,实时更新Redux状态和本地状态:
import { useEffect } from "react"; const ShippingInformation = () => { const dispatch = useDispatch(); const [formStateChanged, setFormStateChanged] = useState(false); const setErrorState = (errorState, data) => { if (errorState === false) { dispatch(setShippingAddress(data)); } if ( (!formStateChanged && !errorState) || (formStateChanged && errorState) ) { return; } else { setFormStateChanged(errorState); dispatch(setShippingAddressError(errorState)); } }; return ( <Formik initialValues={{ address: "", postalCode: "", city: "", country: "" }} validationSchema={Yup.object({ // 保持原验证规则不变 address: Yup.string() .required("This field is required.") .min(2, "This address is too short."), postalCode: Yup.string() .required("This field is required.") .min(2, "This postal code is too short."), city: Yup.string() .required("This field is required.") .min(2, "This city name is is too short."), country: Yup.string() .required("This field is required.") .min(2, "This country name is too short."), })} > {(formik) => { // 监听Formik的errors、touched、values变化 useEffect(() => { const hasNoErrors = Object.keys(formik.errors).length === 0; const hasEnoughTouched = Object.keys(formik.touched).length >= 2; const isValid = hasNoErrors && hasEnoughTouched; setErrorState(!isValid, formik.values); }, [formik.errors, formik.touched, formik.values, formStateChanged]); return ( <VStack as="form"> {/* 移除FormControl上的onChange */} <FormControl> <TextField name="address" placeholder="Street Address" label="Street Address" /> <Flex> <Box flex="1" mr="10"> <TextField name="postalCode" placeholder="Postal Code" label="Postal Code" /> </Box> <Box flex="2"> <TextField name="city" placeholder="City" label="City" /> </Box> </Flex> <TextField name="country" placeholder="Country" label="Country" /> </FormControl> {/* 示例:根据Formik状态控制按钮禁用 */} <Button type="submit" disabled={Object.keys(formik.errors).length > 0 || Object.keys(formik.touched).length < 2} mt="20px" > 确认地址 </Button> {/* 原Shipping Method部分保持不变 */} <Box width="100%" height="180px" pr="5px"> <Heading fontSize="2xl" fontWeight="extrabold" mb="10px"> Shipping Method </Heading> <RadioGroup defaultValue="false" onChange={(event) => { dispatch(setExpress(event)); }} > <Stack direction={{ base: "column", lg: "row" }} align={{ lg: "flex-start" }} > <Stack pr="10" spacing={{ base: "8px", md: "10px" }} flex="1.5"> <Box> <Radio value="true"> <Text fontWeight="bold">Express Shipping $14.99</Text> <Text>Shipped within 24 hours</Text> </Radio> </Box> <Stack spacing="6px"> <Text>Express</Text> </Stack> </Stack> <Radio value="false"> <Tooltip label="Free shipping for all orders over $1,000."> <Box> <Text fontWeight="bold">Standard Shipping $4.99</Text> <Text>Shipped within two to three business days</Text> </Box> </Tooltip> </Radio> </Stack> </RadioGroup> </Box> </VStack> ); }} </Formik> ); };
方案2:直接在按钮上使用Formik状态判断禁用
如果不需要额外的formStateChanged本地状态,可直接在按钮的disabled属性中根据Formik的实时状态判断,完全省去自定义的setErrorState逻辑:
<Button type="submit" disabled={ Object.keys(formik.errors).length > 0 || Object.keys(formik.touched).length < 2 } onClick={() => { // 提交时直接dispatch表单值 dispatch(setShippingAddress(formik.values)); }} mt="20px" > 确认地址 </Button>
关键说明
- Formik的
errors、touched、values都是实时更新的状态,通过useEffect监听这些值的变化,能确保每次输入后立即触发状态同步,不会出现滞后。 - 永远不要在React组件的渲染阶段直接调用
setState或类似状态更新函数,必须通过事件回调或useEffect等生命周期钩子触发。
内容的提问来源于stack exchange,提问作者user19703227
相关产品推荐
相关产品推荐

