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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 17:45:31