如何在依赖前置查询时正确使用useQueries获取Pokémon数据?
问题:React Query useQueries 正确使用方式
我正在开发一个Web应用,初始页面加载时通过PokéAPI展示宝可梦列表,列表用MUI Card显示每个宝可梦的图片和名称。
我用到两个API:
- 获取宝可梦名称及基础列表的API
- 获取特定宝可梦图片详情的API
实现逻辑是先调用第一个API拿到列表,再用列表里的每个宝可梦的URL调用第二个API获取图片。
下面是我写的两个组件:
PokemonList.jsx
import Box from "@mui/material/Box"; import { useQueries, useQuery } from "@tanstack/react-query"; import axios from "axios"; import PokemonCard from "./PokemonCard"; const getPokemonList = async () => { const { data } = await axios.get( "https://pokeapi.co/api/v2/pokemon?limit=12&offset=0" ); console.log(data); return data; }; const usePokemonList = () => { return useQuery(["pokemonList"], () => getPokemonList()); }; const getPokemons = async (url) => { const { data } = await axios.get(url); return data; }; const usePokemons = (pokemons) => { return useQueries({ queries: pokemons?.results.map((pokemon) => { return { queryKey: ["pokemons", pokemon.name], queryFn: getPokemons.bind(this, pokemon.url), staleTime: Infinity, enabled: !!pokemons.count, }; }), }); }; function PokemonList() { const { data: pokemonList } = usePokemonList(); const { data: pokemons, isLoading } = usePokemons(pokemonList); return ( <Box sx={{ display: "flex", justifyContent: "space-evenly", flexFlow: "row wrap", gap: "2em", }} > {!isLoading && pokemons.map((pokemon) => ( <PokemonCard key={pokemon.species.name} name={pokemon.species.name} image={pokemon.sprites.front_default} /> ))} </Box> ); } export default PokemonList;
PokemonCard.jsx
import { Card, CardContent, CardMedia, Typography } from "@mui/material"; import PropTypes from "prop-types"; PokemonCard.propTypes = { image: PropTypes.string, name: PropTypes.string, }; function PokemonCard(props) { return ( <Card sx={{ width: 225, flexWrap: "wrap" }}> <CardMedia component="img" height="140" image={props.image} /> <CardContent> <Typography variant="caption">{props.name}</Typography> </CardContent> </Card> ); } export default PokemonCard;
现在遇到的问题是第一个API还没返回结果时就触发了第二个API调用,导致报错。请问这里正确使用useQueries的方式是什么?
解决方案
问题核心在于对useQueries的返回值结构理解有误,且未正确处理查询的加载状态,修正后的代码如下:
修正后的 usePokemons 钩子
const usePokemons = (pokemonList) => { return useQueries({ queries: pokemonList?.results?.map((pokemon) => ({ queryKey: ["pokemons", pokemon.name], queryFn: () => getPokemons(pokemon.url), staleTime: Infinity, enabled: !!pokemonList, })) || [], }); };
修正后的 PokemonList 组件
function PokemonList() { const { data: pokemonList, isLoading: listLoading } = usePokemonList(); const pokemonQueries = usePokemons(pokemonList); // 过滤已成功获取的宝可梦数据 const pokemons = pokemonQueries .filter(query => query.data) .map(query => query.data); // 判断所有子查询是否加载完成 const allLoaded = pokemonQueries.every(query => !query.isLoading); if (listLoading) { return <div>加载列表中...</div>; } return ( <Box sx={{ display: "flex", justifyContent: "space-evenly", flexFlow: "row wrap", gap: "2em", }} > {pokemons.map((pokemon) => ( <PokemonCard key={pokemon.id} name={pokemon.name} image={pokemon.sprites.front_default} /> ))} {!allLoaded && <div>加载宝可梦详情中...</div>} </Box> ); }
关键修改点说明
useQueries返回值处理:它返回的是单个查询状态的数组,每个元素包含data、isLoading等属性,不能直接解构全局的data和isLoading。- 子查询启用条件:将
enabled设为!!pokemonList,确保只有第一个查询返回结果后才执行子查询。 - 加载状态分层处理:分别处理列表加载和子查询加载的状态,避免数据未就绪时执行
map操作。 - 简化
queryFn:用箭头函数直接传递URL,无需bind绑定上下文。 - 更可靠的key:改用宝可梦的
id作为列表项key,比名称更唯一稳定。
内容的提问来源于stack exchange,提问作者user8126831
相关产品推荐
相关产品推荐

