React-Redux Toolkit组件无法渲染更新后的状态值问题
React-Redux工单应用提交表单后组件未同步显示表单值问题
我使用React-Redux开发工单应用,仪表板页面渲染所有已提交的工单,点击按钮可打开模态表单提交新工单。遇到的问题是:提交表单后,新工单项会在页面渲染,但表单填写的标题和描述未显示在工单项中,只有刷新页面后才会正常显示。通过React DevTools和Redux DevTools确认,刷新前Redux状态已更新,但组件未同步正确的值。尝试在模态表单的onSubmit中调用getTicket无效,仅useNavigate(0)刷新页面能解决,但不想使用这种方式。
相关代码
仪表板页面
import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { useSelector, useDispatch } from "react-redux"; import { getTicket, reset } from "../features/tickets/ticketsSlice"; import TicketsItem from "../components/TicketsItem"; import Spinner from "../components/Spinner"; import SubmitTicketModal from "../components/SubmitTicketModal"; function Dashboard() { const [isOpen, setIsOpen] = useState(false); const openModal = () => { setIsOpen(true); }; const navigate = useNavigate(); const dispatch = useDispatch(); const { user } = useSelector((state) => state.auth); const { tickets, isLoading, isError, message } = useSelector( (state) => state.tickets ); useEffect(() => { if (isError) { console.log(message); } if (!user) { navigate("/login"); } dispatch(getTicket()); return () => { dispatch(reset); // 此处存在问题 }; }, [user, navigate, isError, message, dispatch]); if (isLoading) { return <Spinner />; } return ( <> <section className='dashboard'> <h1 className='dashboard-heading'>Welcome, {user && user.name}!</h1> <div className='dashboard-content'> <div className='dashboard-btn-container'> <button className='btn-primary' onClick={openModal}> New Ticket </button> </div> <section> {tickets.length > 0 ? ( tickets.map((ticket, index) => ( <TicketsItem ticket={ticket} key={index} /> )) ) : ( <h3>No open tickets.</h3> )} </section> </div> </section> {isOpen && <SubmitTicketModal closeModal={setIsOpen} />} </> ); } export default Dashboard;
模态框组件
import { useState } from "react"; import { useDispatch } from "react-redux"; import { createTicket } from "../features/tickets/ticketsSlice"; function SubmitTicketModal({ closeModal }) { const [formData, setFormData] = useState({ title: "", description: "", }); const { title, description } = formData; const onChange = (e) => { setFormData((prevState) => ({ ...prevState, [e.target.name]: e.target.value, })); }; const dispatch = useDispatch(); const onSubmit = (e) => { e.preventDefault(); const ticketData = { title, description, }; dispatch(createTicket(ticketData)); setFormData(""); // 此处存在问题 closeModal(false); }; const closeModalWindow = () => { closeModal(false); }; return ( <> <div className='modal-container'> <div className='modal-box'> <button className='close-modal-btn' onClick={closeModalWindow}> X </button> <h1>Submit New Ticket</h1> <div className='form-group'> <form onSubmit={onSubmit}> <label htmlFor='title'>Subject:</label> <input type='text' id='title' name='title' onChange={onChange} /> <label htmlFor='description'>Describe your issue:</label> <textarea name='description' id='description' resize='none' // 此处存在问题 onChange={onChange}></textarea> <button className='btn-primary' type='submit'> Submit New Ticket </button> </form> </div> </div> </div> </> ); } export default SubmitTicketModal;
工单项组件
function TicketsItem({ ticket }) { return ( <> <div className='ticket-item'> <div className='ticket-heading'> <div className='ticket-heading-title-container'> <h3 className='ticket-title'>{ticket.title}</h3> <p className='ticket-id'>(id: {ticket._id})</p> </div> <div className='ticket-status'> <p>Solved</p> </div> </div> <div className='ticket-date'>Submitted on: Mon, September 20, 2022</div> <div className='ticket-body'>{ticket.description}</div> </div> </> ); } export default TicketsItem;
工单Slice
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; import ticketsService from "./ticketsService"; const initialState = { tickets: [], isError: false, isSuccess: false, isLoading: false, message: "", }; export const createTicket = createAsyncThunk( "tickets/create", async (ticketData, thunkAPI) => { try { const token = thunkAPI.getState().auth.user.token; return await ticketsService.createTicket(ticketData, token); } catch (error) { const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString(); return thunkAPI.rejectWithValue(message); } } ); export const getTicket = createAsyncThunk( "tickets/getAll", async (_, thunkAPI) => { try { const token = thunkAPI.getState().auth.user.token; return await ticketsService.getTickets(token); } catch (error) { const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString(); return thunkAPI.rejectWithValue(message); } } ); export const ticketsSlice = createSlice({ name: "ticket", initialState, reducers: { reset: (state) => initialState, }, extraReducers: (builder) => { builder .addCase(createTicket.pending, (state) => { state.isLoading = true; }) .addCase(createTicket.fulfilled, (state, action) => { state.isLoading = false; state.isSuccess = true; state.tickets.push(action.payload); // 此处可能存在问题 }) .addCase(createTicket.rejected, (state, action) => { state.isLoading = false; state.isError = true; state.message = action.payload; }) .addCase(getTicket.pending, (state) => { state.isLoading = true; }) .addCase(getTicket.fulfilled, (state, action) => { state.isLoading = false; state.isSuccess = true; state.tickets = action.payload; }) .addCase(getTicket.rejected, (state, action) => { state.isLoading = false; state.isError = true; state.message = action.payload; }); }, }); export const { reset } = ticketsSlice.actions; export default ticketsSlice.reducer;
修复方案
1. 修复模态框表单的受控组件绑定与重置逻辑
模态框中的输入框未绑定value属性,属于非受控组件,可能导致输入值未正确同步;同时重置表单数据的方式错误,将对象改为空字符串会破坏后续输入逻辑。
修改模态框组件:
// 修复onSubmit中的表单重置 const onSubmit = (e) => { e.preventDefault(); const ticketData = { title, description, }; dispatch(createTicket(ticketData)); // 重置为初始对象格式 setFormData({ title: "", description: "" }); closeModal(false); }; // 修复输入框和textarea的value绑定与resize属性 return ( // ...其余代码不变 <input type='text' id='title' name='title' onChange={onChange} value={title} // 添加value绑定 /> <textarea name='description' id='description' style={{ resize: 'none' }} // 用style设置resize onChange={onChange} value={description} // 添加value绑定 ></textarea> // ...其余代码不变 )
2. 修复Dashboard的cleanup函数调用
reset是action creator函数,需要执行后再dispatch,否则无法触发状态重置。
修改Dashboard的useEffect:
return () => { dispatch(reset()); // 调用reset函数 };
3. 确保Redux状态中新增工单包含完整字段
如果后端创建工单接口仅返回_id等标识字段,未返回title和description,则需要在Redux的createTicket.fulfilled中合并提交的表单数据与返回结果:
修改工单Slice的extraReducers:
.addCase(createTicket.fulfilled, (state, action) => { state.isLoading = false; state.isSuccess = true; // 合并提交的表单数据和接口返回的工单数据 state.tickets.push({ ...action.meta.arg, ...action.payload }); })
如果后端接口返回完整的工单对象(包含title、description),则此步骤可省略。
完成以上修复后,提交表单后新工单的内容会立即在页面上正确显示,无需刷新页面。
内容的提问来源于stack exchange,提问作者orreryNull
相关产品推荐
相关产品推荐

