Redux Toolkit中filterSlice未配置进Store致ViewProducts页面报错
Redux Toolkit 商品筛选时出现 TypeError:无法读取 undefined 的 slice/length 属性
我正在开发基于Firebase的电商网站,后台管理员路由已完成商品添加功能,现在要实现商品查看、筛选、编辑等功能。作为Redux Toolkit新手,在实现商品搜索筛选时遇到了以下问题:
- 已创建
filterSlice.jsx实现搜索、分类、价格排序等筛选逻辑 - 在
ViewProducts.jsx中通过useSelector调用selectFilteredProducts选择器获取筛选后的商品列表 - 控制台持续抛出
TypeError,提示无法读取undefined的slice或length属性
问题原因
排查后确认,核心问题是store.jsx的combineReducers中未注册filterReducer。Redux store中不存在filter状态分支,导致selectFilteredProducts选择器返回undefined,后续访问filteredProducts的length或调用slice()时自然触发报错。
解决方案
在store.jsx的rootReducer中添加filter: filterReducer,将filter切片正式注册到Redux store中:
修正后的store.jsx
import { configureStore, combineReducers } from "@reduxjs/toolkit" import authReducer from "../redux/slice/authSlice" import productReducer from "../redux/slice/productSlice" import filterReducer from "../redux/slice/filterSlice" const rootReducer = combineReducers({ auth: authReducer, product: productReducer, filter: filterReducer, // 新增该行,注册filter状态分支 }) const store = configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false, }), }); export default store
额外代码问题提示
你的ViewProducts.jsx中还有几个小问题需要修正,避免后续报错:
isLoading变量未定义,需补充初始化或从Redux状态中获取- 列表循环时参数名与解构变量不匹配:循环参数写的是
products,但解构用了product,会导致product为undefined imageURl拼写错误,应为imageURL,否则图片无法正常加载- 建议给
filteredProducts添加可选链判断,避免状态未初始化时报错
修正后的ViewProducts.jsx关键部分
import React, { useEffect, useState } from "react"; // ...其他导入代码 const ViewProducts = () => { const [search, setSearch] = useState("") const [isLoading, setIsLoading] = useState(false); // 初始化isLoading const products = useSelector(selectProducts); const filteredProducts = useSelector(selectFilteredProducts); // 分页状态 const [currentPage, setCurrentPage] = useState(1); const [productsPerPage, setProductsPerPage] = useState(10); // 获取当前页商品,添加可选链避免undefined报错 const indexOfLastProduct = currentPage * productsPerPage; const indexOfFirstProduct = indexOfLastProduct - productsPerPage; const currentProducts = filteredProducts?.slice( indexOfFirstProduct, indexOfLastProduct ) || []; const dispatch = useDispatch() // 监听搜索词变化,触发筛选 useEffect(() => { dispatch(FILTER_BY_SEARCH({ products, search })); }, [dispatch, products, search]); return ( <> {isLoading && <Loader />} <div> <h2>All Products</h2> <p> <b>{filteredProducts?.length || 0}</b> products found </p> {(filteredProducts?.length || 0) === 0 ? ( <p>NO Products found</p> ) : ( <div className="table container-fluid "> <table className="table "> <thead className="product-list justify-content-between align-items-center text-center"> <tr> <th className=" col">S/N</th> <th className=" col-2">Image</th> <th className=" col-4">Name</th> <th className=" col-2">Category</th> <th className=" col-2">Price</th> <th className=" col-2">Action</th> </tr> </thead> <tbody className="table-body justify-content-between align-items-center text-center"> {/* 修正循环参数名,匹配解构变量 */} {currentProducts.map((product, index) => { const { id, name, price, imageURL, category } = product; // 修正图片字段拼写 return ( <tr key={id}> <td>{index + 1}</td> <td> <img src={imageURL} alt={name} style={{ width: "100px" }} /> </td> <td>{name}</td> <td>{category}</td> <td>{`$${price}`}</td> <td> <FaEdit size={20} color="green" /> <FaTrashAlt size={18} color="red" /> </td> </tr> ); })} </tbody> </table> </div> )} </div> </> ); }; export default ViewProducts;
完整filterSlice.jsx参考
import { createSlice } from "@reduxjs/toolkit"; const initialState = { filteredProducts: [] }; const filterSlice = createSlice({ name: "filter", initialState, reducers: { FILTER_BY_SEARCH(state, action) { const { products, search } = action.payload; const tempProducts = products.filter( (product) => product.name.toLowerCase().includes(search.toLowerCase()) || product.category.toLowerCase().includes(search.toLowerCase()) ); state.filteredProducts = tempProducts; }, SORT_PRODUCTS(state, action) { const { products, sort } = action.payload; let tempProducts = []; if (sort === "latest") { tempProducts = products; } if (sort === "lowest-price") { tempProducts = products.slice().sort((a, b) => { return a.price - b.price; }); } if (sort === "highest-price") { tempProducts = products.slice().sort((a, b) => { return b.price - a.price; }); } if (sort === "a-z") { tempProducts = products.slice().sort((a, b) => { return a.name.localeCompare(b.name); }); } if (sort === "z-a") { tempProducts = products.slice().sort((a, b) => { return b.name.localeCompare(a.name); }); } state.filteredProducts = tempProducts; }, FILTER_BY_CATEGORY(state, action) { const { products, category } = action.payload; let tempProducts = []; if (category === "All") { tempProducts = products; } else { tempProducts = products.filter( (product) => product.category === category ); } state.filteredProducts = tempProducts; }, FILTER_BY_BRAND(state, action) { const { products, brand } = action.payload; let tempProducts = []; if (brand === "All") { tempProducts = products; } else { tempProducts = products.filter((product) => product.brand === brand); } state.filteredProducts = tempProducts; }, FILTER_BY_PRICE(state, action) { const { products, price } = action.payload; let tempProducts = []; tempProducts = products.filter((product) => product.price <= price); state.filteredProducts = tempProducts; } } }); export const { FILTER_BY_SEARCH, SORT_PRODUCTS, FILTER_BY_CATEGORY, FILTER_BY_BRAND, FILTER_BY_PRICE } = filterSlice.actions; export const selectFilteredProducts = (state) => state.filter && state.filter.filteredProducts; export default filterSlice.reducer;
内容的提问来源于stack exchange,提问作者Cable-Nerd
相关产品推荐
相关产品推荐

