React Context+React Hook Form表单二次编辑默认值异常问题
问题:二次编辑表单时文本域无法加载最新的post内容
我开发了一个包含新增/编辑模式的表单,在编辑模式下,文本域的默认值为post.content。首次编辑帖子可正常完成,但进行二次编辑时,文本域的默认值仍为旧的post.content状态,而非最新编辑后的值。若在两次编辑前刷新页面则无此问题。
相关代码
PostsContext.tsx
const reducer = (state: PostsState, action: PostsAction) => { switch (action.type) { case 'SET_POSTS': return { posts: action.payload } case 'ADD_POST': return { posts: [action.payload, ...state.posts] } case 'UPDATE_POST': return { posts: state.posts.map((post) => { if (post.id === action.payload.id) { return action.payload } return post }), } case 'DELETE_POST': return { posts: state.posts.filter((post) => post.id !== action.payload.id), } default: return state } }
Posts.tsx
const Posts = ({ post }: Props) => { const { dispatch } = usePostsContext() const { register, handleSubmit, control, watch, reset, formState: { isSubmitting, errors }, } = useForm<FormInput>({ defaultValues: { content: post?.content || '', }, resolver: yupResolver(postSchema), }) const onSubmit: SubmitHandler<FormInput> = async (data) => { const formData = new FormData() formData.append('content', data.content) data.thumbnailFile && formData.append('thumbnailFile', data.thumbnailFile[0]) const response = await fetch( `${import.meta.env.VITE_API_URL}/api/posts/${post ? post.id : ''}`, { method: post ? 'PATCH' : 'POST', credentials: 'include', body: formData, } ) const json = await response.json() if (response.ok) { dispatch({ type: post ? 'UPDATE_POST' : 'ADD_POST', payload: json }) reset() post && setUpdateMode(false) } } return ( <> <Avatar /> {!updateMode && ( <div className="dropdown dropdown-end"> <label tabIndex={0} className="btn btn-square"> ... </label> <ul tabIndex={0} className="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52" > <li> <span onClick={() => setUpdateMode(true)}>Edit</span> </li> <li> <span onClick={handleDelete}>Delete</span> </li> </ul> </div> )} {updateMode ? ( <form onSubmit={handleSubmit(onSubmit)}> <div className="flex flex-col"> <textarea className="w-full h-32 p-2 border border-gray-300 rounded-lg" placeholder="What's on your mind?" {...register('content', { required: true })} /> </div> <div className="divider"></div> {post ? ( <div className="flex items-center"> <button className="btn btn-outline btn-ghost mr-2" onClick={() => { reset(), setUpdateMode(false) }} > Cancel </button> <button type="submit" className="btn btn-accent" disabled={isSubmitting}> {isSubmitting ? 'Saving...' : 'Save'} </button> </div> ) : ( <button type="submit" className="btn btn-accent" disabled={isSubmitting}> {isSubmitting ? 'Posting...' : 'Post'} </button> )} </form> ) : ( post && <Post /> )} </> ) } export default Posts
问题原因
- useForm默认值仅初始化一次:
useForm的defaultValues只会在组件首次挂载时赋值,后续即使postprop更新,默认值也不会自动同步。 - 表单重置未传入最新值:取消编辑时调用
reset()但未传入最新的post.content,导致表单保持旧值状态。 - (可能存在)父组件未传递最新post:若父组件没有从更新后的context中重新获取对应post并传递给
Posts组件,子组件拿到的始终是旧的post对象。
解决方案
方案1:监听post变化自动同步表单值
添加useEffect监听post的变化,当post更新时强制重置表单到最新内容:
useEffect(() => { if (post) { reset({ content: post.content }); } }, [post, reset]);
方案2:打开编辑模式时手动同步最新值
修改"Edit"按钮的点击事件,打开编辑模式的同时重置表单到最新post内容:
<span onClick={() => { reset({ content: post.content }); setUpdateMode(true); }}>Edit</span>
方案3:确保父组件传递最新post
检查父组件逻辑,确保context中的posts更新后,父组件重新通过find等方法获取对应id的最新post,再传递给Posts子组件。
额外验证点
- 确认
UPDATE_POSTaction返回的json是后端返回的最新post数据,避免context更新的数据本身有误。 - 当前reducer中
UPDATE_POST已经通过map返回新对象,保证了引用变化,无需额外处理对象不可变性问题。
内容的提问来源于stack exchange,提问作者Tambouil
相关产品推荐
相关产品推荐

