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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 09:15:22