You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题原因

  1. useForm默认值仅初始化一次:useForm的defaultValues只会在组件首次挂载时赋值,后续即使post prop更新,默认值也不会自动同步。
  2. 表单重置未传入最新值:取消编辑时调用reset()但未传入最新的post.content,导致表单保持旧值状态。
  3. (可能存在)父组件未传递最新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_POST action返回的json是后端返回的最新post数据,避免context更新的数据本身有误。
  • 当前reducer中UPDATE_POST已经通过map返回新对象,保证了引用变化,无需额外处理对象不可变性问题。

内容的提问来源于stack exchange,提问作者Tambouil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 20:40:48