React Hook Form重渲染后无法显示默认值问题
问题
react-hook-form无法加载从localStorage中获取的默认值。这些默认值通过useEffect读取localStorage后更新unuploadedShoeData状态,但组件重渲染后表单并未同步这些值。
已知情况:
- 若默认值在表单初始渲染前就存在(比如硬编码值),表单能正常显示默认值
- 日志确认unuploadedShoeData已被正确设置,但表单未触发重渲染
原因分析
核心问题在于:
fields数组在组件每次渲染时重新创建,但自定义的Form组件(react-hook-form封装)仅在初始化阶段读取initial值,后续fields变化不会触发表单更新默认值- 当
unuploadedShoeData从null更新为实际数据时,shoe变量会变化,但Form组件未感知到该变化,也未重新初始化表单值
解决方案
提供两种可行方案,可根据实际场景选择:
方案一:给Form组件添加key触发重初始化
当shoe变化时,通过修改key属性让Form组件重新挂载,从而读取新的initial值:
<Form key={shoe ? JSON.stringify(shoe) : 'empty'} fields={fields} onSubmit={edit ? editShoe : uploadShoe} submitButtonText={edit ? "Update" : "Sell"} />
方案二:在Form内部监听fields变化并调用reset
若不想让Form组件重新挂载,可在自定义Form组件中添加逻辑,监听fields变化并调用react-hook-form的reset方法更新表单值:
// 自定义Form组件内部示例 import { useForm, useEffect } from 'react-hook-form'; const Form = ({ fields, onSubmit, submitButtonText }) => { const { register, handleSubmit, reset } = useForm(); useEffect(() => { // 从fields中提取最新初始值 const initialValues = fields.reduce((acc, field) => { acc[field.name] = field.initial; return acc; }, {}); reset(initialValues); }, [fields, reset]); // 表单渲染逻辑... };
额外优化建议
- 用
useMemo缓存shoe计算结果,避免每次渲染重复执行过滤操作:
const shoe = useMemo(() => { return edit ? shoes.filter((shoe) => shoe.id === parseInt(id)).pop() : unuploadedShoeData ?? null; }, [edit, shoes, id, unuploadedShoeData]);
- 用
useMemo缓存fields数组,依赖shoe和shoeOptions,避免不必要的数组重新创建:
const fields = useMemo(() => [ // 原fields数组内容 ], [shoe, shoeOptions]);
修改后的完整代码
import { useMemo, useEffect, useState } from 'react'; // 其他必要导入... const ShoeUpload = ({ edit = false, user, setShoes, shoeOptions, api, shoes, }) => { const classes = useStyles(); const navigate = useNavigate(); const { id } = useParams(); const [loading, setLoading] = useState(false); const [unuploadedShoeData, setUnuploadedShoeData] = useState(null); const UNUPLOADED_SHOE_DATA_KEY = "unuploadedShoeData"; // 缓存shoe计算结果 const shoe = useMemo(() => { return edit ? shoes.filter((shoe) => shoe.id === parseInt(id)).pop() : unuploadedShoeData ?? null; }, [edit, shoes, id, unuploadedShoeData]); console.log("shoe", shoe); useEffect(() => { const shoeData = localStorage.getItem(UNUPLOADED_SHOE_DATA_KEY); if (shoeData) { setUnuploadedShoeData(JSON.parse(shoeData)); } }, []); const _storeUnuploadedShoeData = (shoeData) => { const toStore = { brand: shoeData.brand.value, condition: shoeData.condition, foot: shoeData.foot.value, gender: shoeData.gender.value, icon: shoeData.icon, images: shoeData.images, price: shoeData.price, size: shoeData.size.value, }; localStorage.setItem(UNUPLOADED_SHOE_DATA_KEY, JSON.stringify(toStore)); }; const uploadShoe = (data) => { if (unuploadedShoeData) { localStorage.removeItem(UNUPLOADED_SHOE_DATA_KEY); } setLoading(true); api .createShoe( user.id, data.brand.value, data.icon, data.foot.value, data.gender.value, data.size.value, data.condition, data.price, data.images ) .then((shoe) => { setLoading(false); setShoes((prevShoes) => [...prevShoes, shoe]); Analytics.uploadShoe(shoe); navigate(`/shoes/${shoe.id}`); }); }; const editShoe = (data) => { setLoading(true); api .editShoe( shoe.id, data.brand.value, data.icon, data.foot.value, data.gender.value, data.size.value, data.condition, data.price, data.images, shoe.images ) .then((shoe) => { setShoes((prevShoes) => prevShoes.map((prevShoe) => prevShoe.id === shoe.id ? shoe : prevShoe ) ); setLoading(false); navigate(`/shoes/${shoe.id}`); }); }; // 缓存fields数组 const fields = useMemo(() => [ { name: "brand", label: "Shoe Brand", required: true, component: "autocomplete", options: shoeOptions?.brand, initial: shoe ? shoeOptions?.brand ?.filter((brand) => brand.value === shoe.brand) .pop() : null, }, { name: "icon", label: "Shoe Icon", required: true, initial: shoe ? shoe.icon : null, }, { name: "foot", label: "Foot", required: true, component: "autocomplete", options: shoeOptions?.foot, initial: shoe ? shoeOptions?.foot?.filter((foot) => foot.value === shoe.foot).pop() : null, }, { name: "gender", label: "Gender", required: true, component: "autocomplete", options: shoeOptions?.gender, initial: shoe ? shoeOptions?.gender ?.filter((gender) => gender.value === shoe.gender) .pop() : null, }, { name: "size", label: "Size (UK)", required: true, component: "autocomplete", options: shoeOptions?.size, initial: shoe ? shoeOptions?.size?.filter((size) => size.value === shoe.size).pop() : null, }, { name: "price", label: "Price (£)", required: true, type: "number", initial: shoe ? shoe.price : null, }, { name: "condition", label: "Condition", required: true, multiline: true, initial: shoe ? shoe.condition : null, }, { name: "images", label: "Images", required: true, component: "imageUpload", acceptedFiles: ["image/jpeg", "image/png"], filesLimit: 6, initial: shoe ? shoe.images?.map((image) => image.image) : null, }, ], [shoe, shoeOptions]); return ( <div> {loading ? ( <Grid container justifyContent="center" alignItems="center" direction="column" minHeight="80vh" > <Grid item xs={12} my={2}> <Typography variant="body1">Just a second...</Typography> </Grid> <Grid item xs={12}> <CircularProgress /> </Grid> </Grid> ) : ( <Box container className={classes.container}> <Card variant="outlined" className={classes.card}> <CardContent> <Typography variant="h6" component="h1" textAlign="center" mb={2}> {edit ? "Edit shoe" : "Upload a shoe to sell"} </Typography> <Form key={shoe ? JSON.stringify(shoe) : 'empty'} fields={fields} onSubmit={edit ? editShoe : uploadShoe} submitButtonText={edit ? "Update" : "Sell"} /> </CardContent> </Card> </Box> )} </div> ); }; export default ShoeUpload;
内容的提问来源于stack exchange,提问作者Yazan
相关产品推荐
相关产品推荐

