I'm using the following function to get data (results) from the pokemon api:
const [data, setData] = useState({ results: []})
useEffect(() => {
const fetchData = async () => {
const response = await api.get('/pokemon');
setData(response.data);
};
fetchData();
}, []);
My API function:
const api = axios.create({
baseURL: 'https://pokeapi.co/api/v2'
});
And this is the response I have
{
"count": 964,
"next": "https://pokeapi.co/api/v2/pokemon?offset=20&limit=20",
"previous": null,
//data that I need to use
"results": [
{
"name": "bulbasaur",
"url": "https://pokeapi.co/api/v2/pokemon/1/"
//the get request in the URL above can work both with name or number in pokedex.
},
//the list goes on till the 20th pokemon.
]
How can I perform a get request from the url that's inside of the object in results array?
If you want the response and the details for each individual pokemon from you API you map over the response.results and use Promise.all to make a series of API calls and resolve it.
useEffect(() => {
const fetchData = async () => {
const response = await api.get('/pokemon');
// if you want to get the details as well
// details.data will be an array with details for all the pokemon that you get
// from response
const details = await Promise.all(response.data.results.map((el) => {
return axios.get(`/pokemon/${el.name}`)
}));
};
fetchData();
}, []);
const fetchData = async () => {
const response = await api.get('/pokemon');
setData(response.data);
const { url } = response.data.results[0];
const reponse2 = axios.get(url);
// do what you want with response2
};
alternatively, you may want to loop through the results
for(const result of response.data.results){
const { url } = result;
const reponse = axios.get(url);
// do something with response
}
Related
I am doing something like this, where I retrieve the data of a fund which contains an id, then I query the server again using the retrieved id to retrieve data from another table:
const [backendData, setBackendData] = useState({})
const [fundData, setFundData] = useState({})
useEffect(() => {
async function fetchFund(){
console.log("fetching fund...");
var query = getQueryVariable('fund');
await fetch(`http://localhost:24424/api/funds?fund=${query}`).then(
response => response.json()
).then(
data =>{
setBackendData(data);
}
)
}
async function fetchData(){
var fundId = backendData[0]?._id ?? "fundid";
await fetch(`http://localhost:24424/api/funds/data?id=${fundId}`).then(
response => response.json()
).then(
data =>{
setFundData(data);
}
)
}
fetchFund();
fetchData();
}, [])
The problem is that in the fetchData() function, fundId is always equal to the fallback value fundid when the page first loads, so the server query fails. When using {fundId} later on in the page, it works fine as the value is eventually retrieved. How can I tell React to wait for backendData[0]?._id to be present before executing the fetchData() function?
You should separate the two function so one depend of the existing of the other your code will look something like this
const [backendData, setBackendData] = useState({})
const [fundData, setFundData] = useState({})
useEffect(() => {
async function fetchFund(){
console.log("fetching fund...");
var query = getQueryVariable('fund');
await fetch(`http://localhost:24424/api/funds?fund=${query}`).then(
response => response.json()
).then(
data =>{
setBackendData(data);
}
)
}
fetchFund();
}, [])
useEffect(() => {
if(!backendData?.[0]?._id) return;
async function fetchData(){
var fundId = backendData[0]?._id ?? "fundid";
await fetch(`http://localhost:24424/api/funds/data?id=${fundId}`).then(
response => response.json()
).then(
data =>{
setFundData(data);
}
)
}
fetchData();
}, [backendData])
Now fetchData will only get called when at least one backendData is available
I'm new to react-query and I'm trying to move all of my API calls into a new file, out of the useQuery calls.
Unfortunately when I do this all of my data is undefined.
I do see the network calls in the network tab, it just isn't being set properly in useQuery.
Thanks in advance for any help on how to change my code to fix this!
// this works
const { loading, data, error } = useQuery([conf_id], async () => {
const { data } = await axios.get(API_URL + '/event/' + conf_id)
return data
});
// this doesn't work - data is undefined
const axios = require('axios');
const getEventById = async () => {
const { data } = await axios.get(API_URL + '/event/2541' + '?noyear=true');
return data.data;
};
const { loading, data, error } = useQuery('conf_id', getEventById});
// the below variants don't work either
// const { loading, data, error } = useQuery('conf_id', getEventById()});
// const { loading, data, error } = useQuery('conf_id', async () => await getEventById()});
// const { loading, data, error } = useQuery('conf_id', async () => await
// const { data } = getEventById(); return data
// });
An AxiosResponse has a data attribute from which you can access the actual API JSON response.
Like you pointed out, this:
async () => {
const { data } = await axios.get(API_URL + '/event/' + conf_id)
return data
}
Should suffice for the fetching function.
So the final implementation should look like
const axios = require('axios');
const getEventById = async () => {
const { data } = await axios.get(API_URL + '/event/2541' + '?noyear=true');
return data;
};
const { loading, data, error } = useQuery('conf_id', getEventById);
The data you get from the useQuery should be undefined on the first render and once the server responds it will change to whatever the response is.
I have multiple api's that are returning same type of data but from different areas but the data type is the same only the values are different, and I want to store them all in one react state.
So I have this state:
let [infoData1, setInfoData1] = useState({ infoData1: [] });
let [infoData2, setInfoData2] = useState({ infoData2: [] });
and the axios calls :
function multipleApiCall() {
const headers = {
"X-Api-Key": "the-api-key-00",
};
axios.get(
"http:url-to-data/ID1",
{ headers }
)
.then((response) => {
setInfoData1(response.data);
return axios.get(
"http:url-to-data/ID2",
{ headers }
)
})
.then(response => {
setInfoData2(response.data);
})
}
and afterward I want to use a .map() to list the result but because I have 2 states I cannot concatenate them. So how can I have all data from those two api's in just one state or maybe another approach ?
const [infoData, setInfoData] = useState([]);
const headers = {
"X-Api-Key": "the-api-key-00",
};
const urls = ["http:url-to-data/ID1", "http:url-to-data/ID2"];
function multipleApiCall() {
const promises = urls.map(url => axios.get(url, { headers }));
Promise.all(promises).then(responses => {
let data = [];
responses.forEach(response => {
data = data.concat(response.data);
});
setInfoData(data);
});
}
I have a component which displays products for a category. CategoryId is taken from subscribe method which is formed by pubsub pattern so I am waiting sub function to finish and passing to my API but it is not working on intial load of the page?
import { subscribe } from "./pubsub";
const Test = () => {
const [productId, setProductId] = useState({});
const [response, setResponse] = useState([]);
React.useEffect(() => {
function sub() {
return new Promise((resolve, reject) => {
subscribe("product-message", (data) => {
// console.log("Got some message", data);
// setProductId(data.productId);
resolve(data.productId);
});
});
}
async function fetchData() {
let message = await sub();
let response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${message.productId}` // Here I couldn't get the async data from above useEffect
);
console.log(response);
setResponse(response);
}
fetchData();
}, []);
return <div>{response.title}</div>; //It is not printing in intial load
};
export default Test;
So here is my sandbox link: https://codesandbox.io/s/happy-forest-to9pz?file=/src/test.jsx
If you only need the response, you do not need to store productId in state and then use it in another useEffeect to fetch data. You can simply implement the logic in one useEffec. Also note that you need to use the json response from fetch call so you need to use it like
let response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}`
).then(res => res.json());
or
let res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}`
)
let response = await res.json();
Complete function will look like
const Test = () => {
const [response, setResponse] = useState([]);
React.useEffect(() => {
async function fetchData(productId) {
let response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}`
).then(res => res.json());
console.log(response);
setResponse(response);
}
console.log("Api calls");
subscribe("product-message", (data) => {
// console.log("Got some message", data);
fetchData(data.productId);
});
}, []);
return <div>{response.title}</div>;
};
export default Test;
However if you need productId in your application, you can go via a multiple useEffect approach like you have tried in your sandbox. Also make sure that you are using thee fetch call correctly and also make sure to not make the API call wheen productId is not available
const Test = () => {
const [productId, setProductId] = useState({});
const [response, setResponse] = useState([]);
React.useEffect(() => {
console.log("Api calls");
subscribe("product-message", (data) => {
// console.log("Got some message", data);
setProductId(data.productId);
});
}, []);
React.useEffect(() => {
async function fetchData() {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${productId}` // Here I couldn't get the async data from above useEffect
);
const response = await res.json();
console.log(response);
setResponse(response);
}
if(productId) {
fetchData();
}
}, [productId]);
return <div>{response.title}</div>;
};
export default Test;
Working Sandbox
I have a function that fetches from a url in React
const DataContextProvider = (props) => {
const [isLoading, setLoading] = useState(false);
const [cocktails, setCocktails] = useState([]);
useEffect(() => {
const fetchCocktailList = async () => {
const baseUrl = 'https://www.thecocktaildb.com/api/json/v1/1/';
setLoading(true);
try {
const res = await fetch(`${baseUrl}search.php?s=margarita`);
const data = await res.json();
console.log(data);
setCocktails(data.drinks);
setLoading(false);
} catch (err) {
console.log('Error fetching data');
setLoading(false);
}
};
fetchCocktailList();
}, []);
How I'm mapping data so far.
const DrinkList = () => {
const { cocktails } = useContext(DataContext);
return (
<div className='drink-list-wrapper'>
{cocktails.length > 0 &&
cocktails.map((drink) => {
return <DrinkItem drink={drink} key={drink.idDrink} />;
})}
</div>
);
};
However I want to fetch from this url also ${baseUrl}search.php?s=martini
I would like a good clean way to do this and set my state to both of the returned data.
First base the data fetch function on a parameter:
const fetchCocktail = async (name) => {
const baseUrl = 'https://www.thecocktaildb.com/api/json/v1/1/';
try {
const res = await fetch(`${baseUrl}search.php?s=` + name);
const data = await res.json();
return data.drinks;
} catch (err) {
console.log('Error fetching data');
}
}
Then use Promise.all to await all results:
setLoading(true);
var promises = [
fetchCocktail(`margarita`),
fetchCocktail(`martini`)
];
var results = await Promise.all(promises);
setLoading(false);
DrinkList(results);
Where results will be an array with the responses that you can use on the DrinkList function.
Here's a method which will let you specify the cocktail names as dependencies to the useEffect so you can store them in your state and fetch new drink lists if you want new recipes. If not, it'll just be a static state variable.
I've also added another state variable errorMessage which you use to pass an error message in the case of failure.
Also, you should include the appropriate dependencies in your useEffect hook. The setState functions returned by calls to useState are stable and won't trigger a re-run of the effect, and the cocktailNames variable won't trigger a re-run unless you update it with new things to fetch.
const DataContextProvider = (props) => {
const [isLoading, setLoading] = useState(false);
const [cocktails, setCocktails] = useState([]);
const [errorMessage, setErrorMessage] = useState(''); // holds an error message in case the network request dosn't succeed
const [cocktailNames, setCocktailNames] = useState(['margarita', 'martini']); // the search queries for the `s` parameter at your API endpoint
useEffect(() => {
const fetchCocktailLists = async (...cocktailNames) => {
const fetchCocktailList = async (cocktailName) => {
const baseUrl = 'https://www.thecocktaildb.com/api/json/v1/1/search.php';
const url = new URL(baseUrl);
const params = new URLSearchParams({s: cocktailName});
url.search = params.toString(); // -> '?s=cocktailName'
const res = await fetch(url.href); // -> 'https://www.thecocktaildb.com/api/json/v1/1/search.php?s=cocktailName'
const data = await res.json();
const {drinks: drinkList} = data; // destructured form of: const drinkList = data.drinks;
return drinkList;
};
setLoading(true);
try {
const promises = [];
for (const cocktailName of cocktailNames) {
promises.push(fetchCocktailList(cocktailName));
}
const drinkLists = await Promise.all(promises); // -> [[drink1, drink2], [drink3, drink4]]
const allDrinks = drinkLists.flat(1); // -> [drink1, drink2, drink3, drink4]
setCocktails(allDrinks);
}
catch (err) {
setErrorMessage(err.message /* or whatever custom message you want */);
}
setLoading(false);
};
fetchCocktailList(...cocktailNames);
}, [cocktailNames, setCocktails, setErrorMessage, setLoading]);
};
var promises = [
fetchCocktail(api1),
fetchCocktail(api2)
];
var results = await Promise.allSettled(promises);