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

Redux-Toolkit与React数据流异常求助:CRUD操作问题

问题概述
  • createProduct 可将产品存入数据库,但经常停留在pending状态;偶尔执行成功触发product/create/fulfilled后,会立即报错“error state.product.push is not a function”,无法进入下一状态
  • deleteProduct 能删除产品,但同样卡在pending状态;触发product/delete/fulfilled后报错“error state.product.filter is not a function”
  • getProduct 的prevState、action和nextState数据正确,但页面始终渲染上一个产品的数据,且文本值在getProduct启动前就已渲染

Redux Slice 代码

import {createSlice, createAsyncThunk} from '@reduxjs/toolkit'
import { RootState } from '../../app/store'
import productsService from './productsService'
import {UpdateProductData} from '../../pages/ProductEdit'
export interface Product{
    _id?:string,
    id?:string,
    image:string,
    title:string,
    producer:string,
    categories:string[],
    desc:string,
    price:string,
    currency:string,
    colors:string[],
    sizes:string[],
    inStock:boolean,
    createdAt:Date,
    updatedAt?:Date,
    accessToken?:string,
}
export interface InitialState{
    product:Product[],
    allProducts:Product[],
    isLoading:boolean,
    isSuccess:boolean,
    isError:boolean,
    message:string,
}
const initialState: InitialState ={
    product:[],
    allProducts:[],
    isLoading:false,
    isSuccess:false,
    isError:false,
    message:"",
}
type AsyncThunkConfig = {
    state:RootState
}
export const createProduct = createAsyncThunk<Product, FormData, AsyncThunkConfig>('/product/create', async (productData, thunkAPI) => {
    try{
        const token:string = thunkAPI.getState().auth.user!.accessToken;
        return await productsService.createProduct(productData, token);
    } catch(error:any){
        const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString()
      return thunkAPI.rejectWithValue(message as string)
    }
})
export const updateProduct = createAsyncThunk<Product[], UpdateProductData, AsyncThunkConfig>('/product/update', async (updateProductData, thunkAPI)=>{
    try{
        const token:string = thunkAPI.getState().auth.user!.accessToken;
        return await productsService.updateProduct(updateProductData, token);
    } catch(error:any){
        const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString()
      return thunkAPI.rejectWithValue(message as string)
    }
})

export const deleteProduct = createAsyncThunk<Product, string, AsyncThunkConfig>('product/delete', async (Id, thunkAPI)=>{
    try{
        const token = thunkAPI.getState().auth.user!.accessToken;
        return await productsService.deleteProduct(Id, token);
    } catch(error:any){
        const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString()
      return thunkAPI.rejectWithValue(message as string)
    }
})
export const getProduct = createAsyncThunk<Product[], string, AsyncThunkConfig>('product/find', async (Id, thunkAPI)=>{
    try{
        return await productsService.getProduct(Id);
    } catch(error:any){
        const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString()
      return thunkAPI.rejectWithValue(message as string)
    }
})
export const getAllProducts = createAsyncThunk<Product[], void, AsyncThunkConfig>('/product/findAll', async (_, thunkAPI)=>{
    try{
        return await productsService.getAllProducts();
    } catch(error:any){
        const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString()
      return thunkAPI.rejectWithValue(message as string)
    }
})

export const productsSlice = createSlice({
    name:"products",
    initialState,
    reducers:{
        reset:(state)=>initialState,
    },
    extraReducers(builder) {
      builder
      .addCase(createProduct.pending, (state)=>{
        state.isLoading = true; 
      })
      .addCase(createProduct.fulfilled, (state, action)=>{
        state.isLoading = false;
        state.isSuccess = true;
        state.product.push(action.payload);
      })
      .addCase(createProduct.rejected, (state,action:any)=>{
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
      .addCase(updateProduct.pending, (state)=>{
        state.isLoading = true;
      })
      .addCase(updateProduct.fulfilled, (state, action)=>{
        state.isLoading = false;
        state.isSuccess = true;
        state.product = action.payload;
      })
      .addCase(updateProduct.rejected, (state, action:any)=>{
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
      .addCase(deleteProduct.pending, (state)=>{
        state.isLoading = true;
      })
      .addCase(deleteProduct.fulfilled, (state, action)=>{
        state.isLoading = false;
        state.isSuccess = true;
        state.product = state.product.filter((item)=>item._id !== action.payload.id);
      })
      .addCase(deleteProduct.rejected, (state, action:any)=>{
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
      .addCase(getProduct.pending, (state)=>{
        state.isLoading = true;
      })
      .addCase(getProduct.fulfilled, (state, action)=>{
        state.isLoading = false;
        state.isSuccess = true;
        state.product = action.payload;
      })
      .addCase(getProduct.rejected, (state, action:any)=>{
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
      .addCase(getAllProducts.pending, (state)=>{
        state.isLoading = true;
      })
      .addCase(getAllProducts.fulfilled, (state, action)=>{
        state.isLoading = false;
        state.isSuccess = true;
        state.allProducts = action.payload;
      })
      .addCase(getAllProducts.rejected, (state, action:any)=>{
        state.isLoading = false;
        state.isError = true;
        state.message = action.payload;
      })
    }
})

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

Redux Service 代码

import axios from 'axios';
import { UpdateProductData } from '../../pages/ProductEdit';
const API_URL = 'http://localhost:5001/api/products/';

const createProduct = async (productData:FormData, token:string)=>{
    const config = {
        headers:{
            'Content-Type':"multipart/form-data",
            token: `Bearer ${token}`,
        }
    }
    const response =  await axios.post(API_URL, productData, config);
    console.log(response.data);//immediatly correct data
    return response.data;
}
const updateProduct = async (updateProductData:UpdateProductData, token:string)=>{
    const config ={
        headers:{
            'Content-Type':"multipart/form-data",
            token:`Bearer ${token}`
        }
    }
    const response = await axios.put(API_URL+updateProductData.id, updateProductData.productData, config);
    console.log(response.data);
    return response.data;
}
const deleteProduct = async (id:string, token:string)=>{
    const config = {
        headers:{
            token:`Bearer ${token}`
        }
    }
    const response = await axios.delete(API_URL + id, config);
    console.log(response.data);
    return response.data;
}
const getProduct = async (id:string)=>{
    const getUrl = `find/${id}`;
    const response = await axios.get(API_URL+getUrl);
    console.log(response.data)
    return response.data;
}
const getAllProducts = async ()=>{
    const response = await axios.get(API_URL+ 'find');
    console.log(response.data);
    return response.data;
}
const productsService = {
    createProduct,
    updateProduct,
    deleteProduct,
    getProduct,
    getAllProducts
}
export default productsService

Store 配置代码

import { configureStore, ThunkAction, Action} from '@reduxjs/toolkit';
import authReducer from '../features/authSlice';
import userReducer from '../features/user/userSlice'
import productReducer from '../features/products/productsSlice';
import descriptionItemReducer from '../features/descriptionItems/descriptionItemSlice';
import sliderItemsReducer from '../features/sliderItems/sliderItemSlice';
import storage from 'redux-persist/lib/storage'
import {persistReducer} from 'redux-persist'

const persistConfig = {
  key: 'root',
  version: 1,
  storage,
}
const persistedUserReducer = persistReducer(persistConfig,userReducer) 
const persistedProductReducer = persistReducer(persistConfig, productReducer)
const persistedSliderReducer = persistReducer(persistConfig, sliderItemsReducer)
export const store = configureStore({
  reducer: {
    auth:authReducer,
    user:persistedUserReducer,
    products:persistedProductReducer,
    descriptionItem:descriptionItemReducer,
    sliderItems:persistedSliderReducer,
  },
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;

后端API代码

import {Router, Request, Response} from 'express';
const productsRouter = Router();
import {verifyTokenAndAdmin} from '../middleware/jwtVerify';
import Products from '../models/products';
import upload from '../utils/multer';
const cloudinary = require('../utils/cloudinary');
import path from 'path'
productsRouter.post('/', upload.single('image'), verifyTokenAndAdmin, async (req:Request, res:Response)=>{
    console.log(req.file);
    console.log(req.body);
    let fileUrl = req.file!.path.replace(/\\/g, "/");
    console.log(fileUrl);
    try{
        const uploadResult = await cloudinary.uploader.upload(fileUrl, {
            upload_preset: "webshop_ts_mern",
            resource_type: "auto",
        })
        
        const newProducts = new Products({
            cloudinary_id: uploadResult.public_id,
            title: req.body.title,
            producer: req.body.producer,
            categories: JSON.parse(req.body.categories).split(' '),
            desc: req.body.desc,
            price: req.body.price,
            currency:req.body.currency,
            colors:JSON.parse(req.body.colors).split(' '),
            sizes: JSON.parse(req.body.sizes).split(' '),
            inStock: req.body.inStock,
            image: uploadResult.secure_url,

        })
        console.log(newProducts);
        const savedproducts = await newProducts.save();
        res.status(200).json(savedproducts);
    } catch(error){
        res.status(403)
        console.log(error);
        throw new Error("Action failed");
    }
});
//update
productsRouter.put('/:id',upload.single("image"), verifyTokenAndAdmin, async (req:Request, res:Response)=>{
    console.log(req.file);
    console.log(req.body)

    try{
        let updatedProducts = await Products.findById(req.params.id);
        if(req.file){
        await cloudinary.uploader.destroy(updatedProducts?.cloudinary_id);
        }
       let result;
        if(req.file){
            let fileUrl = req.file!.path.replace(/\\/g, "/");
        result = await cloudinary.uploader.upload(fileUrl, {
            upload_preset: "webshop_ts_mern",
            resource_type: "auto",
        })
        }
        const updatedData = {
            title: req.body.title || updatedProducts!.title,
            producer: req.body.producer || updatedProducts!.producer,
            categories: JSON.parse(req.body.categories) || updatedProducts!.categories,
            desc: req.body.desc || updatedProducts!.desc,
            price: req.body.price || updatedProducts!.price,
            currency: req.body.currency || updatedProducts!.currency,
            colors: JSON.parse(req.body.colors) || updatedProducts!.colors,
            sizes: JSON.parse(req.body.sizes) || updatedProducts!.sizes,
            inStock: req.body.inStock || updatedProducts!.inStock,
            cloudinary_id: result ? result.public_id : updatedProducts!.cloudinary_id,
            image: result ? result.secure_url : updatedProducts!.image,
        }
        console.log(updatedData);
        updatedProducts = await Products.findByIdAndUpdate(req.params.id, updatedData, {
            new:true,
        })
        res.status(200).json(updatedProducts);
    } catch(error){
        res.status(404)
        console.log(error);
        throw new Error('Not found')
    }
});
//delete
productsRouter.delete('/:id', verifyTokenAndAdmin, async (req:Request, res:Response)=>{
    try{
        let deleteProducts = await Products.findById(req.params.id);
        await cloudinary.uploader.destroy(deleteProducts!.cloudinary_id);
         await deleteProducts!.remove();
        res.status(200).json("Produkt wurde gelöscht");
    } catch(error){
        res.status(404)
        throw new Error("Nicht gefunden")
    }
});
//get
productsRouter.get('/find/:id', async (req:Request, res:Response)=>{
    try{
        const products = await Products.findById(req.params.id);
        res.status(200).json(products)
    } catch(error){
        res.status(404)
        throw new Error("Nicht gefunden");
    }
});
//get All
productsRouter.get('/find/', async (req:Request, res:Response)=>{
        try{
        const allProducts = await Products.find()
        res.status(200).json(allProducts);
    } catch(error){
        res.status(404)
        throw new Error("Not found");
    }
})

export default productsRouter;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 02:05:18