Reputation: 23
I am creating a webapp where I can show a list of Pokemons on initial page load using PokéAPI. The list should show all the images of the Pokemons and its name, inside of an MUI Card, something like this: App UI example.
I am using two APIs. One for getting the list of all the Pokemon names and their details. Another for getting the specific Pokemon's image.
In order to create the UI, I am querying the first API to get the list of all the Pokemons. And then I am using the result of the first API in the second query to get the image of that Pokemon.
Here are both the components that I have created:
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;
This is the error that I am getting, because the first API has not resolved yet. What should be the proper way to use useQueries
here?
Upvotes: 2
Views: 2127
Reputation: 6633
The error is telling you that the thing you're trying to map for your useQueries
call is null or undefined. Which makes sense if the previous result has not yet returned. Additionally your hooks are a little too brittle in how their integrated. I would suggest refactoring your usePokemons
hook to just take a list of pokemons (not an object with results) and default it to being empty. e.g.
const usePokemons = (pokemons) => {
pokemons = pokemons || [];
return useQueries({
queries: pokemons.map((pokemon) => {
return {
queryKey: ["pokemons", pokemon.name],
queryFn: getPokemons.bind(this, pokemon.url),
staleTime: Infinity,
};
}),
});
};
Then you just change how you integrate the two:
function PokemonList() {
const { data: pokemonList, isLoading: isListLoading } = usePokemonList();
const { data: pokemons, isLoading: isImagesLoading } = usePokemons(pokemonList?.results);
// ... etc
Upvotes: 3