React中使用Redux Toolkit传递数据至Action,实现用户专属分类获取
问题:用户专属分类列表无法正确过滤,返回所有分类
我需要从MongoDB中展示分类列表,要求每个用户仅能查看自己的专属分类。当前前端采用React.js与Redux Toolkit,后端使用Node.js。但目前调用接口获取数据时,返回了所有分类,而非当前登录用户的分类。
后端代码
categoryController.js
// Retrieve and return all categories. exports.findAll = async (req, res) => { const { pageIndex, pageSize, sort, query, userId } = req.body; const { order, key } = sort; // query the Category model and filter by user Category.find({ User: userId }, (err, categories) => { if (err) { return res.status(500).send(err); } let data = categories; let total = categories.length; // sort data if necessary if ((key === 'name' || key === 'description') && order) { data.sort(datatable.sortBy(key, order === 'desc', (a) => a.toUpperCase())); } else { data.sort(datatable.sortBy(key, order === 'desc', parseInt)); } // apply wildcard search if necessary if (query) { data = datatable.wildCardSearch(data, query); total = data.length; } data = datatable.paginate(data, pageSize, pageIndex); // return response res.send({ data: data, total: total }); }); }
路由配置
router.post("/list", categoryController.findAll);
前端代码
React组件(CategoryTable.js)
import React, { useEffect, useMemo } from 'react' import { DataTable } from 'components/shared' import { HiOutlinePencil, HiOutlineTrash } from 'react-icons/hi' import { useDispatch, useSelector } from 'react-redux' import { getCategories, setTableData } from '../store/dataSlice' import { setSortedColumn, setSelectedCategory } from '../store/stateSlice' import { toggleDeleteConfirmation } from '../store/stateSlice' import useThemeClass from 'utils/hooks/useThemeClass' import CategoryDeleteConfirmation from './CategoryDeleteConfirmation' import { useNavigate } from 'react-router-dom' import cloneDeep from 'lodash/cloneDeep' const ActionColumn = ({ row }) => { const dispatch = useDispatch() const { textTheme } = useThemeClass() const navigate = useNavigate() const onEdit = () => { navigate(`/events/category/edit/${row.id}`) } const onDelete = () => { dispatch(toggleDeleteConfirmation(true)) dispatch(setSelectedCategory(row.id)) } return ( <div className="flex justify-end text-lg"> <span className={`cursor-pointer p-2 hover:${textTheme}`} onClick={onEdit} > <HiOutlinePencil /> </span> <span className="cursor-pointer p-2 hover:text-red-500" onClick={onDelete} > <HiOutlineTrash /> </span> </div> ) } const CategoryTable = () => { const dispatch = useDispatch() const { pageIndex, pageSize, sort, query, total } = useSelector( (state) => state.GategoriesList.data.tableData, ) const userInfo = useSelector((state) => state.auth.user) const filterData = useSelector( (state) => state.GategoriesList.data.filterData, ) const loading = useSelector((state) => state.GategoriesList.data.loading) const data = useSelector((state) => state.GategoriesList.data.GategoriesList) const fetchData = () => { dispatch(getCategories({ pageIndex, pageSize, sort, query, filterData, userId:userInfo._id })) // console.log({pageIndex, pageSize, sort, query, filterData, userId:userInfo._id}) } useEffect(() => { fetchData() // eslint-disable-next-line react-hooks/exhaustive-deps }, [pageIndex, pageSize, sort]) const tableData = useMemo( () => ({ pageIndex, pageSize, sort, query, total }), [pageIndex, pageSize, sort, query, total], ) const columns = useMemo( () => [ { Header: 'Category', accessor: 'name', sortable: true, Cell: (props) => { const row = props.row.original return <span className="capitalize">{row.name}</span> }, }, { Header: 'Description', accessor: 'description', sortable: true, Cell: (props) => { const row = props.row.original return <span className="capitalize">{row.description}</span> }, }, { Header: '', id: 'action', accessor: (row) => row, Cell: (props) => <ActionColumn row={props.row.original} />, }, ], [], ) const onPaginationChange = (page) => { const newTableData = cloneDeep(tableData) newTableData.pageIndex = page dispatch(setTableData(newTableData)) } const onSelectChange = (value) => { const newTableData = cloneDeep(tableData) newTableData.pageSize = Number(value) newTableData.pageIndex = 1 dispatch(setTableData(newTableData)) } const onSort = (sort, sortingColumn) => { const newTableData = cloneDeep(tableData) newTableData.sort = sort dispatch(setTableData(newTableData)) dispatch(setSortedColumn(sortingColumn)) } return ( <> <DataTable columns={columns} data={data} skeletonAvatarColumns={[0]} skeletonAvatarProps={{ className: 'rounded-md' }} loading={loading} pagingData={tableData} onPaginationChange={onPaginationChange} onSelectChange={onSelectChange} onSort={onSort} /> <CategoryDeleteConfirmation /> </> ) } export default CategoryTable
Redux Data Slice(dataSlice.js)
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' import { apiGetCategories, apiDeleteCategory } from 'services/CategoriesService' export const getCategories = createAsyncThunk('categories/data/getCategories',async (data) => { const response = await apiGetCategories(data) return response.data }) export const deleteCategory = async (data) => { const response = await apiDeleteCategory(data) return response.data } export const initialTableData = { userId: '', total: 0, pageIndex: 1, pageSize: 10, query: '', sort: { order: '', key: '' } } export const initialFilterData = { name: '', category: ['name', 'description'], status: [0, 1, 2], CatgoriesStatus: 0, } const dataSlice = createSlice({ name: 'categories/data', initialState: { loading: false, GategoriesList: [], tableData: initialTableData, filterData: initialFilterData, }, reducers: { updateCategoryList: (state, action) => { state.GategoriesList = action.payload }, setTableData: (state, action) => { state.tableData = action.payload }, setFilterData: (state, action) => { state.filterData = action.payload }, }, extraReducers: { [getCategories.fulfilled]: (state, action) => { state.GategoriesList = action.payload.data state.tableData.total = action.payload.total state.loading = false }, [getCategories.pending]: (state) => { state.loading = true }, } }) export const { updateCategoryList, setTableData, setFilterData, setSortedColumn } = dataSlice.actions export default dataSlice.reducer
问题排查与修复方案
1. 数据库字段名不匹配
后端代码中使用{ User: userId }作为查询条件,但MongoDB区分字段大小写,若你的Category集合中存储用户关联的字段是userId或user(小写),则过滤条件会失效,返回所有分类。
修复:
将查询条件改为与数据库实际字段名一致,例如:
// 假设数据库字段为userId Category.find({ userId: userId }, (err, categories) => { // ... 原有逻辑 })
2. 前端userId传递验证
取消前端代码中console.log的注释,检查控制台输出的userId是否与当前登录用户ID一致;同时确认Redux的auth.user状态中确实存储了用户的_id字段。
3. 后端参数安全优化
当前通过req.body获取userId存在被篡改风险,建议从验证后的用户令牌(如JWT)中提取用户ID:
// 假设已通过JWT中间件将用户信息挂载到req.user exports.findAll = async (req, res) => { const { pageIndex, pageSize, sort, query } = req.body; const userId = req.user._id; // 从验证后的用户信息中获取ID const { order, key } = sort; Category.find({ userId: userId }, (err, categories) => { // ... 原有逻辑 }); }
4. 分页排序性能优化
当前先查询所有数据再在内存中处理排序分页,数据量大时性能差。建议直接通过MongoDB原生查询完成排序和分页:
exports.findAll = async (req, res) => { const { pageIndex, pageSize, sort, query, userId } = req.body; const { order, key } = sort; try { // 构建查询条件 let queryFilter = { userId: userId }; if (query) { queryFilter.$or = [ { name: { $regex: query, $options: 'i' } }, { description: { $regex: query, $options: 'i' } } ]; } // 构建排序规则 const sortOption = {}; sortOption[key] = order === 'desc' ? -1 : 1; // 分页查询 const total = await Category.countDocuments(queryFilter); const categories = await Category.find(queryFilter) .sort(sortOption) .skip((pageIndex - 1) * pageSize) .limit(pageSize); res.send({ data: categories, total: total }); } catch (err) { res.status(500).send(err); } }
内容的提问来源于stack exchange,提问作者Anas Boukricha
相关产品推荐
相关产品推荐

