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状态更新导致组件重渲染,就出现了自动提交空帖子的问题。
修复步骤
- 给Clear按钮添加
type="button",明确它是普通按钮,不会触发表单提交 - (可选优化)使用
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
相关产品推荐
相关产品推荐

