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

React+Redux Toolkit删除文章后UI未自动更新问题排查

问题描述

我用React和Redux Toolkit开发博客应用,删除文章时,数据库里已经移除目标内容,Redux Toolkit也收到了MongoDB返回的成功消息,但点击删除按钮后文章不会立刻从UI消失,必须手动刷新页面才行。请帮忙找出问题所在。


实现代码

postSlice.js

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import postService from './postService';

const initialState = {
  posts: [],
  isError: false,
  isSuccess: false,
  isLoading: false,
  message: '',
};

// Create a post
export const createPost = createAsyncThunk(
  'posts/create',
  async (postData, thunkAPI) => {
    try {
      const token = thunkAPI.getState().auth.user.token;
      return await postService.createPost(postData, token);
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

// Get all posts
export const getPosts = createAsyncThunk(
  'posts/getAll',
  async (_, thunkAPI) => {
    try {
      return await postService.getPosts();
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

// Delete post
export const deletePost = createAsyncThunk(
  'posts/delete',
  async (id, thunkAPI) => {
    try {
      const token = thunkAPI.getState().auth.user.token;
      return await postService.deletePost(id, token);
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

export const postSlice = createSlice({
  name: 'post',
  initialState,
  reducers: {
    reset: (state) => initialState,
  },
  extraReducers: (builder) => {
    builder
      .addCase(createPost.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(createPost.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isSuccess = true;
        state?.posts.push(action.payload);
      })
      .addCase(createPost.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
      .addCase(getPosts.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(getPosts.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isSuccess = true;
        state.posts = action.payload;
      })
      .addCase(getPosts.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
      .addCase(deletePost.pending, (state) => {
        state.isLoading = true;
      })
      .addCase(deletePost.fulfilled, (state, action) => {
        state.isLoading = false;
        state.isSuccess = true;
        state.posts = state.posts.filter((post) => post._id !== action.payload);
        console.log(action.payload);
      })
      .addCase(deletePost.rejected, (state, action) => {
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      });
  },
});

export const { reset } = postSlice.actions;
export default postSlice.reducer;

postService.js

import axios from 'axios';

const API_URL = '/api/posts/';

// Create a post
const createPost = async (postData, token) => {
  const config = {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  };

  const response = await axios.post(API_URL, postData, config);

  return response.data;
};

// Get all posts
const getPosts = async () => {
  const response = await axios.get(API_URL);

  return response.data;
};

// Delete a post
const deletePost = async (postId, token) => {
  const config = {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  };

  const response = await axios.delete(API_URL + postId, config);

  return response.data;
};

const postService = {
  createPost,
  getPosts,
  deletePost,
};

export default postService;

Post.js

import React from 'react';
import './post.css';
import { FaSmileBeam, FaRegSmile } from 'react-icons/fa';
import { RiEdit2Fill } from 'react-icons/ri';
import { MdDeleteOutline } from 'react-icons/md';
import moment from 'moment';
import { useDispatch } from 'react-redux';
import {
  deletePost,
} from '../../features/posts/postSlice';

const Post = ({ post }) => {
  const dispatch = useDispatch();
  const user = JSON.parse(localStorage.getItem('user'));

    return (
      <>
        <FaRegSmile className="smile__icon" />
        &nbsp;
      </>
    );
  };
  const removePost = () => {
    dispatch(deletePost(post._id));
  };

  const { title, createdAt, body, imageFile, postCreator, author } = post;

  return (
    <div className="post__container card">
      <div className="post__card">
        <div className="post__image">
          <img src={imageFile} alt="post" />
        </div>
        <div className="post__content">
          <div className="edit__icon">
            {author === user?.result?._id && (
              <RiEdit2Fill className="edit__icon" fontSize="small" />
            )}
          </div>
          <div className="post__header__title">
            <h3>{title}</h3>
          </div>
          <div className="post__message">
            <p>{body}</p>
          </div>
          <div className="post__header">
            <div className="post__header__date">
              {user ? (
                <span className="user__avatar">
                  <p>{user?.result?.username.charAt(0).toUpperCase()}</p>
                </span>
              ) : (
                ''
              )}
              <span className="post__header__date__text">
                {postCreator.toUpperCase()}
                <p className="moments">{moment(createdAt).fromNow()}</p>
              </span>
            </div>
          </div>
          <div className="post__actions">
            <div className="icon__box">
              <Smile disabled={!user} />
            </div>
            <div className="icon__box">
              <MdDeleteOutline className="delete__icon" onClick={removePost} />
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default Post;

问题分析与解决方案

核心问题

UI不自动更新的原因是Redux状态更新逻辑不匹配:

  • postService.deletePost返回的是后端接口的成功消息(比如字符串"删除成功"),但在postSlice的deletePost.fulfilled回调里,你用action.payload(也就是这个成功消息)去和文章的_id做过滤匹配,条件永远不成立,导致posts数组没有被修改,UI自然不会更新。

修复方案

方案一:修改Redux异步操作返回值

直接在deletePost的异步逻辑里返回传入的文章ID,而不是后端返回的消息:

export const deletePost = createAsyncThunk(
  'posts/delete',
  async (id, thunkAPI) => {
    try {
      const token = thunkAPI.getState().auth.user.token;
      await postService.deletePost(id, token);
      return id; // 直接返回文章ID,替代后端返回的消息
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

这样action.payload就是被删除文章的ID,filter逻辑能正确过滤掉目标文章,UI会自动更新。

方案二:修改后端接口

让删除接口返回被删除文章的ID,这样postService.deletePost返回的就是ID,原有的Redux状态更新逻辑无需修改。

额外修复点

Post组件里有一段多余的代码会导致渲染异常,需要删除:

// 删掉这段无效代码
    return (
      <>
        <FaRegSmile className="smile__icon" />
        &nbsp;
      </>
    );
  };

另外组件中用到的<Smile />是未定义的,需要替换为FaRegSmile或FaSmileBeam,避免报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 15:15:30