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

Redux Store更新toEditPostId触发全应用重渲染及意外提交问题

Redux Toolkit表单意外提交问题修复

问题描述

我正在通过项目学习Redux Toolkit,使用store中的toEditPostId变量(初始值为null)记录待编辑帖子的ID。将其值设为ID字符串时应用运行正常,但点击表单组件的Clear按钮调用dispatch(setToEditPostId(null))时,整个应用会触发重渲染,且handleSubmit函数被自动调用,进而新增一条空帖子,这不符合我的预期。

原postsSlice代码

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axiosInstance  from "../utils/axiosInstance";

export const fetchMemoryPosts = createAsyncThunk(
  'posts/fetchPosts',
  async (_, thunkApi) => {
    try {
      const response = await axiosInstance.get('/posts');
      return response.data.posts;
    } catch (error) {
      const errorMessage = error.response.data.match(/Error: (.*?)<br>/)[1];
      const errorCode = error.response.status;
      return thunkApi.rejectWithValue({ errorMessage, errorCode });
    }
  }
);

export const createMemoryPost = createAsyncThunk(
  'posts/createPosts',
  async(data, thunkApi) => {
    const tagsArr = data.tags.split(',').map((ele) => ele.trim());
    const preparedData = { ...data, tags: tagsArr };

    try {
      const response = await axiosInstance.post(
        '/posts/create',
        preparedData
      );
      return response.data.data;
    } catch (error) {
      const errorMessage = error.response.data.match(/Error: (.*?)<br>/)[1];
      const errorCode = error.response.status
      return thunkApi.rejectWithValue({ errorMessage, errorCode })
    }
  }
)

const initialState = {
  posts: [],
  status: 'idle',
  error: null,
  // 用于跟踪待编辑帖子ID的状态变量
  toEditPostId: null,
}

const postsSlice = createSlice({
  name: 'posts',
  initialState,
  reducers: {
    // 在这里修改ID值和重置为null
    setToEditPostId(state, action) {
      state.toEditPostId = action.payload;
    }
  },
  extraReducers(builder) {
    builder
      .addCase(fetchMemoryPosts.pending, (state, _) => {
        state.status = 'loading';
      })
      .addCase(fetchMemoryPosts.fulfilled, (state, action) => {
        state.status = 'success';
        state.posts = action.payload;
      })
      .addCase(fetchMemoryPosts.rejected, (state, action) => {
        state.status = 'failure';
        state.error = action.payload;
      })
      .addCase(createMemoryPost.fulfilled, (state, action) => {
        state.status = 'success';
        state.posts = state.posts.concat(action.payload);
      })
      .addCase(createMemoryPost.rejected, (state, action) => {
        state.status = 'failure';
        state.error = action.payload;
      })
  }
});

export const getAllPostsSelector = (state) => state.posts.posts;
export const getPostsErrorSelector = (state) => state.posts.error;
export const getPostsStatusSelector = (state) => state.posts.status;
export const getToEditPostIdSelector = (state) => state.posts.toEditPostId;

export const { setToEditPostId } = postsSlice.actions;

export default postsSlice.reducer;

原Form组件代码

import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
  createMemoryPost,
  setToEditPostId,
  getToEditPostIdSelector,
  getAllPostsSelector
} from '../../features/postsSlice';

const Form = () => {
  const dispatch = useDispatch();

  const allPosts = useSelector(getAllPostsSelector);
  const postId = useSelector(getToEditPostIdSelector);

  let targetPost;
  if (postId) {
    targetPost = allPosts.filter((post) => post._id === postId);
  }

  console.log("pi->", postId)
  console.log("po->", targetPost);


  const initialPostDataState = {
    creator: '', title: '', message:'', tags:''
  }

  const [postData, setPostData] = useState(initialPostDataState);

  // 调用dispatch(setToEditPostId(null))时,这个函数会被意外执行
  const handleSubmit = (e) => {
    console.log("it ran");
    e.preventDefault()
    dispatch(createMemoryPost(postData));

    setPostData(initialPostDataState);
  }

  const handleInput = (e) => {
    setPostData({ ...postData, [e.target.name]: e.target.value })
  }

  const clearForm = () => {
    dispatch(setToEditPostId(null))
    setPostData(initialPostDataState);
  }

  return (
    <main className='bg-transparent_bg w-full flex'>
      <form
        className='w-full text-center space-y-3 p-2 box-border' 
        onSubmit={handleSubmit}
      >
        <h3 className='font-extrabold'>
          {postId !== null? "" : "Create a memory"}
        </h3>
        <input 
          className='input'
          type="text" 
          placeholder='Creator' 
          name='creator'
          value={postData.creator}
          onChange={handleInput}
        />
        <input 
          className='input'
          type="text" 
          placeholder='Title' 
          name='title'
          value={postData.title}
          onChange={handleInput}
        />
        <textarea 
          className='input'
          placeholder='Message'
          name="message"
          cols="30" 
          rows="5"
          value={postData.message}
          onChange={handleInput}
        />
        <input 
          className='input'
          type="text" 
          placeholder='Tags (coma seperated)' 
          name='tags'
          value={postData.tags}
          onChange={handleInput}
        />

        <div className='flex justify-around py-1 box-border'>
          <button
            type='submit'
            className='bg-blue-400 w-24'
          >
            Submit
          </button>
          <button
            // 调用dispatch将值设为null的Clear按钮
            onClick={() => dispatch(setToEditPostId(null))}
            className='bg-red-500 w-24'
          >
            Clear
          </button>
        </div>
      </form>
    </main>
  )
}

export default Form;

问题原因与修复方案

核心问题

表单内的Clear按钮未指定type="button",在HTML中,<button>标签默认的type是submit,点击时会触发表单的onSubmit事件,进而调用handleSubmit函数。再加上Redux状态更新导致组件重渲染,就出现了自动提交空帖子的问题。

修复步骤

  1. 给Clear按钮添加type="button",明确它是普通按钮,不会触发表单提交
  2. (可选优化)使用useCallback包裹事件处理函数,配合useSelector的缓存功能,减少不必要的重渲染

修改后的Form组件代码

import React, { useState, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
  createMemoryPost,
  setToEditPostId,
  getToEditPostIdSelector,
  getAllPostsSelector
} from '../../features/postsSlice';

const Form = () => {
  const dispatch = useDispatch();

  const allPosts = useSelector(getAllPostsSelector);
  const postId = useSelector(getToEditPostIdSelector);

  let targetPost;
  if (postId) {
    targetPost = allPosts.filter((post) => post._id === postId);
  }

  const initialPostDataState = {
    creator: '', title: '', message:'', tags:''
  }

  const [postData, setPostData] = useState(initialPostDataState);

  // 使用useCallback缓存函数,避免重渲染时重新创建
  const handleSubmit = useCallback((e) => {
    console.log("it ran");
    e.preventDefault()
    dispatch(createMemoryPost(postData));
    setPostData(initialPostDataState);
  }, [dispatch, postData, initialPostDataState]);

  const handleInput = useCallback((e) => {
    setPostData(prev => ({ ...prev, [e.target.name]: e.target.value }));
  }, []);

  const clearForm = useCallback(() => {
    dispatch(setToEditPostId(null));
    setPostData(initialPostDataState);
  }, [dispatch, initialPostDataState]);

  return (
    <main className='bg-transparent_bg w-full flex'>
      <form
        className='w-full text-center space-y-3 p-2 box-border' 
        onSubmit={handleSubmit}
      >
        <h3 className='font-extrabold'>
          {postId !== null? "" : "Create a memory"}
        </h3>
        <input 
          className='input'
          type="text" 
          placeholder='Creator' 
          name='creator'
          value={postData.creator}
          onChange={handleInput}
        />
        <input 
          className='input'
          type="text" 
          placeholder='Title' 
          name='title'
          value={postData.title}
          onChange={handleInput}
        />
        <textarea 
          className='input'
          placeholder='Message'
          name="message"
          cols="30" 
          rows="5"
          value={postData.message}
          onChange={handleInput}
        />
        <input 
          className='input'
          type="text" 
          placeholder='Tags (comma separated)' 
          name='tags'
          value={postData.tags}
          onChange={handleInput}
        />

        <div className='flex justify-around py-1 box-border'>
          <button
            type='submit'
            className='bg-blue-400 w-24'
          >
            Submit
          </button>
          <button
            // 添加type="button",阻止默认提交行为
            type="button"
            onClick={clearForm}
            className='bg-red-500 w-24'
          >
            Clear
          </button>
        </div>
      </form>
    </main>
  )
}

export default Form;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 01:22:32