React Redux Toolkit中useGetFilterProductsQuery等钩子未定义问题求助
报错信息
app_api_apiSlice__WEBPACK_IMPORTED_MODULE_1_.useGetFilterProductsByCategoriesQuery is not a function
ProductList@http://localhost:3000/static/js/bundle.js:781:108
...(省略后续栈信息)
用户代码
ProductList.js
import React from 'react'; import { Container, Row, Col, Card, Button } from 'react-bootstrap'; import { useGetProductsQuery, useGetFilterProductsQuery, useGetFilterProductsByCategoriesQuery } from '../../app/api/apiSlice'; import { useSelector } from 'react-redux'; function ProductList() { const selectedCategories = useSelector(state => state.filters.selectedCategories); const minPrice = useSelector(state => state.filters.minPrice); const maxPrice = useSelector(state => state.filters.maxPrice); let productQuery; if (minPrice && maxPrice) { alert(minPrice + " " + maxPrice); productQuery = useGetFilterProductsQuery(minPrice, maxPrice ); } else if (selectedCategories.length > 0) { alert(selectedCategories); productQuery = useGetFilterProductsByCategoriesQuery(selectedCategories); } else { console.log(`It is working fine!`); productQuery = useGetProductsQuery(); } const { data: products, isLoading, isError, isSuccess } = productQuery; if (isLoading) { return <p>Loading...</p>; } if (isError) { console.log('Error' + isError); return <p>Error fetching products</p>; } return ( <Container> <h1 className="my-4">Product List</h1> <Row> {products.products.map((product) => ( <Col key={product.id} xs={12} md={6} lg={4}> <Card className="mb-4"> <Card.Img variant="top" src={product.image} alt={product.name} /> <Card.Body> <Card.Title>{product.name}</Card.Title> <Card.Text>{product.description}</Card.Text> <Card.Text>Price: ${product.price}</Card.Text> <Button variant="primary" onClick={() => handleAddToCart(product.id)} > Add to Cart </Button> </Card.Body> </Card> </Col> ))} </Row> </Container> ); } export default ProductList;
apiSlice.js
// Need to use the React-specific entry point to import createApi import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' // Define a service using a base URL and expected endpoints export const productApi = createApi({ reducerPath: 'productApi', baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:5000/' }), endpoints: (builder) => ({ getProducts: builder.query({ query: () => 'product', // Change this to match your API endpoint for fetching products }), getCategory: builder.query({ query: () => 'categories', // Change this to match your API endpoint for fetching products }), getfilterProducts: builder.query({ query: ({minPrice,maxPrice}) => `/products/filterbypricerange?minPrice=${minPrice}&maxPrice=${maxPrice}`, }), getfilterProductsByCategories: builder.query({ query: (categories) => `/products/categories=${categories}`, }), }), }) // Export hooks for usage in functional components, which are // auto-generated based on the defined endpoints export const { useGetProductsQuery,useGetCategoryQuery, useGetFilterProductsQuery,useGetFilterProductsByCategoriesQuery } = productApi;
问题分析与修复步骤
1. 钩子命名不匹配(核心报错原因)
RTK Query 自动生成的钩子名称遵循 use[EndpointNamePascalCase]Query 规则,其中 EndpointNamePascalCase 是 endpoint 键名的大驼峰形式。
- apiSlice 中 endpoint 键名是
getfilterProductsByCategories(filter首字母小写),自动生成的钩子应为useGetfilterProductsByCategoriesQuery,但导出的是useGetFilterProductsByCategoriesQuery(Filter首字母大写),导致找不到该函数。 - 同理,
getfilterProducts对应的正确钩子是useGetfilterProductsQuery,导出的useGetFilterProductsQuery也不匹配。
修复方案:统一 endpoint 键名的驼峰命名,改为规范的大驼峰开头:
// apiSlice.js 中修改 endpoints 部分 endpoints: (builder) => ({ // ...其他保留 getFilterProducts: builder.query({ query: ({minPrice,maxPrice}) => `/products/filterbypricerange?minPrice=${minPrice}&maxPrice=${maxPrice}`, }), getFilterProductsByCategories: builder.query({ query: (categories) => `/products/categories=${categories}`, }), }),
此时 RTK 会自动生成 useGetFilterProductsQuery 和 useGetFilterProductsByCategoriesQuery 钩子,与导出名称完全匹配。
2. 违反 React Hooks 调用规则
在 if/else 条件语句中调用 RTK Query 钩子,违反了 Hooks 必须在组件顶层调用 的规则,会导致 React 状态异常。
修复方案:在组件顶层调用所有需要的钩子,通过 skip 参数控制是否发起请求,再根据条件选择使用对应的查询结果:
// ProductList.js 中修改查询部分 function ProductList() { const selectedCategories = useSelector(state => state.filters.selectedCategories); const minPrice = useSelector(state => state.filters.minPrice); const maxPrice = useSelector(state => state.filters.maxPrice); // 顶层调用所有钩子,通过 skip 控制请求触发 const { data: allProducts, isLoading: isLoadingAll, isError: isErrorAll } = useGetProductsQuery( {}, { skip: !!minPrice || !!maxPrice || selectedCategories.length > 0 } ); const { data: priceFilteredProducts, isLoading: isLoadingPrice, isError: isErrorPrice } = useGetFilterProductsQuery( { minPrice, maxPrice }, { skip: !minPrice || !maxPrice || selectedCategories.length > 0 } ); const { data: categoryFilteredProducts, isLoading: isLoadingCategory, isError: isErrorCategory } = useGetFilterProductsByCategoriesQuery( selectedCategories, { skip: selectedCategories.length === 0 || !!minPrice || !!maxPrice } ); // 根据条件选择对应的结果 let products, isLoading, isError; if (minPrice && maxPrice) { products = priceFilteredProducts; isLoading = isLoadingPrice; isError = isErrorPrice; } else if (selectedCategories.length > 0) { products = categoryFilteredProducts; isLoading = isLoadingCategory; isError = isErrorCategory; } else { products = allProducts; isLoading = isLoadingAll; isError = isErrorAll; } // ...后续逻辑不变 }
3. 查询参数传递错误
useGetFilterProductsQuery 的 endpoint 定义中,query 函数接收的是对象 {minPrice, maxPrice},但调用时传了两个独立参数,导致参数无法正确解析。
修复方案:传递对象参数(上述修复代码已包含此修改):
useGetFilterProductsQuery({ minPrice, maxPrice }, { skip: ... })
4. 未定义 handleAddToCart 函数
组件中使用了 handleAddToCart 但未定义,点击按钮会报错。
修复方案:补充函数定义(示例,根据实际需求调整):
function ProductList() { // ...其他代码 const handleAddToCart = (productId) => { // 实现添加到购物车的逻辑,比如 dispatch Redux action console.log('Added product to cart:', productId); }; // ...后续逻辑不变 }
最终修正后的核心代码片段
apiSlice.js(关键修改)
export const productApi = createApi({ reducerPath: 'productApi', baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:5000/' }), endpoints: (builder) => ({ getProducts: builder.query({ query: () => 'product', }), getCategory: builder.query({ query: () => 'categories', }), // 修正 endpoint 命名为规范驼峰 getFilterProducts: builder.query({ query: ({minPrice,maxPrice}) => `/products/filterbypricerange?minPrice=${minPrice}&maxPrice=${maxPrice}`, }), // 修正 endpoint 命名为规范驼峰 getFilterProductsByCategories: builder.query({ query: (categories) => `/products/categories=${categories}`, }), }), }) // 导出的钩子现在与自动生成的名称完全匹配 export const { useGetProductsQuery, useGetCategoryQuery, useGetFilterProductsQuery, useGetFilterProductsByCategoriesQuery } = productApi;
ProductList.js(关键修改)
function ProductList() { const selectedCategories = useSelector(state => state.filters.selectedCategories); const minPrice = useSelector(state => state.filters.minPrice); const maxPrice = useSelector(state => state.filters.maxPrice); // 顶层调用所有钩子,通过 skip 控制请求触发 const { data: allProducts, isLoading: isLoadingAll, isError: isErrorAll } = useGetProductsQuery( {}, { skip: !!minPrice || !!maxPrice || selectedCategories.length > 0 } ); const { data: priceFilteredProducts, isLoading: isLoadingPrice, isError: isErrorPrice } = useGetFilterProductsQuery( { minPrice, maxPrice }, { skip: !minPrice || !maxPrice || selectedCategories.length > 0 } ); const { data: categoryFilteredProducts, isLoading: isLoadingCategory, isError: isErrorCategory } = useGetFilterProductsByCategoriesQuery( selectedCategories, { skip: selectedCategories.length === 0 || !!minPrice || !!maxPrice } ); // 选择当前有效的查询结果 let products, isLoading, isError; if (minPrice && maxPrice) { products = priceFilteredProducts; isLoading = isLoadingPrice; isError = isErrorPrice; } else if (selectedCategories.length > 0) { products = categoryFilteredProducts; isLoading = isLoadingCategory; isError = isErrorCategory; } else { products = allProducts; isLoading = isLoadingAll; isError = isErrorAll; } // 补充 handleAddToCart 函数定义 const handleAddToCart = (productId) => { // 替换为你的实际购物车逻辑 console.log(`Added product ${productId} to cart`); }; if (isLoading) { return <p>Loading...</p>; } if (isError) { console.error('Error fetching products:', isError); return <p>Error fetching products</p>; } // 确保 products 存在再渲染 if (!products?.products) return <p>No products found</p>; return ( <Container> <h1 className="my-4">Product List</h1> <Row> {products.products.map((product) => ( <Col key={product.id} xs={12} md={6} lg={4}> <Card className="mb-4"> <Card.Img variant="top" src={product.image} alt={product.name} /> <Card.Body> <Card.Title>{product.name}</Card.Title> <Card.Text>{product.description}</Card.Text> <Card.Text>Price: ${product.price}</Card.Text> <Button variant="primary" onClick={() => handleAddToCart(product.id)} > Add to Cart </Button> </Card.Body> </Card> </Col> ))} </Row> </Container> ); }
内容的提问来源于stack exchange,提问作者COdeingNinja

