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
相关产品推荐
相关产品推荐

