I'm currently trying to understand how to work with async/await in React JS. In this demo app, I'm trying to get full border names of the chosen country by calling https://restcountries.eu/. I make first API call to get info about country and the second one to get full name of its borders since first API call returns
only short border names. I believe there is a way to combine those calls inside one useEffect however everything I tried gave me some sort of an error or getting stuck in infinite loop. What is the proper way to combine those calls with async/await approach?
import React, { useState, useEffect } from "react";
import Axios from "axios";
const App = () => {
const [loading, setLoading] = useState(true);
const [country, setCountry] = useState({});
const [fullBorderNames, setFullBorderNames] = useState([]);
//FIRST API CALL
useEffect(() => {
const source = Axios.CancelToken.source();
const fetchData = async () => {
setLoading(true);
try {
const response = await Axios(
`https://restcountries.eu/rest/v2/name/canada?fullText=true`,
{ cancelToken: source.token }
);
setCountry(response.data[0]);
} catch (err) {
if (Axios.isCancel(err)) {
console.log("Axios canceled");
} else {
console.log(err);
}
}
};
fetchData();
return () => source.cancel();
}, []);
//SECOND API CALL
useEffect(() => {
const source = Axios.CancelToken.source();
let borders = [];
if (country.borders) {
const fetchData = async () => {
try {
country.borders.forEach(async border => {
const response = await Axios(
`https://restcountries.eu/rest/v2/alpha?codes=${border}`,
{ cancelToken: source.token }
);
borders.push(response.data[0].name);
if (borders.length === country.borders.length)
setFullBorderNames(borders);
});
} catch (err) {
if (Axios.isCancel(err)) {
console.log("Axios canceled");
} else {
console.log(err);
}
}
setLoading(false);
};
fetchData();
}
return () => source.cancel();
}, [country.borders]);
if (loading) {
return <h2>Loading</h2>;
} else {
return (
<>
<pre>{JSON.stringify(country, null, 2)}</pre>
<pre>{JSON.stringify(fullBorderNames, null, 2)}</pre>
</>
);
}
};
export default App;
You can simply just make the requests right after the first one.
try {
const response = await Axios(`https://restcountries.eu/rest/v2/name/canada?
fullText=true`, { cancelToken: source.token });
const country = response.data[0];
setCountry(country);
/* all the other fetch calls*/
Can you tell me what kind of errors you get because I don't see an issue with doing them in the same useEffect? It just gets a little messy which can be refactored anyway.
Related
I'm trying to understand how useEffect works.
I have two callApi: "callApiDialer" is based on response of "callApiManager", for get id from list.
But "currentLeadId" state at first called obviously is null.
How can call "callApiDialer" when currentLeadId is not null?
import React, { useState, useEffect } from 'react';
const [loading, setLoading] = useState(true);
const [apiManager, setApiManager] = useState([]);
const [apiDialer, setApiDialer] = useState([]);
const [currentLeadId, setCurrentLeadId] = useState(null);
// CALL API
const callApiManager = async () => {
try {
const response = await api.get(`/api/manager/op/1`);
setCurrentLeadId(response.data.dialer_list[0].id);
setApiManager(response.data);
} catch (err) {
alert("fetchApiManager " + err.response.status);
}
}
const callApiDialer = async () => {
try {
const response = await api.get(`/api/manager/lead/${currentLeadId}`);
setApiDialer(response.data.lead);
setLoadingModal(false);
} catch (err) {
alert("fetchApiSources " + err.response.status);
}
}
useEffect(() => {
callApiManager();
}, [])
useEffect(() => {
console.log(currentLeadId); // <-- here get first null and after currentLeadId
if(currentLeadId) {
callApiDialer();
setLoading(false);
}
}, [currentLeadId])
You could have just one function that call both, therefore there would be only one useEffect.
// CALL API
const callBothApisAtOnce= async () => {
try {
const op = await api.get(`/api/manager/op/1`);
const response = await api.get(`/api/manager/lead/${op.data.dialer_list[0].id}`);
// rest of your logic...
} catch (err) {
alert("err" + err);
}
}
useEffect(() => {
callBothApisAtOnce()
}, [])
you can use axios's promise base functionality
axios.get(`/api/manager/op/1`).then(res => {
setCurrentLeadId(response.data.dialer_list[0].id);
setApiManager(response.data);
axios.get(`/api/manager/lead/${response.data.dialer_list[0].id}`).then(res1 =>{
setApiDialer(res1.data.lead);
setLoadingModal(false);
}
}
I'm making a Quiz app, using API from (Trivia api),
Issues is - As soon as the api call is made the state is changes 3 times and my UI data changes 2 times in 1 second.
I think the issue is related to useEffect even though i'm adding empty dependency in useEffect.
can anybody explain why is it happening?
Layout.js
import { useEffect, useState } from 'react'
import { Outlet } from 'react-router-dom'
import Header from '../Componenets/Header/Header'
import ProgressBar from '../Componenets/ProgressBar/ProgressBar'
import QuizMain from '../Componenets/QuizMain/QuizMain'
function Layout() {
const [questionAre, setQuestionsAre] = useState([])
const [currentQuestion, setCurrentQuestion] = useState(0)
const changeCurrentQuestion = (value) => {
setCurrentQuestion(value)
}
useEffect(() => {
const QuizFetch = async () => {
try {
const res = await fetch(
'https://the-trivia-api.com/api/questions?categories=food_and_drink,general_knowledge&limit=10®ion=AU&difficulty=easy',
)
const data = await res.json()
const transformData = data.map((item) => {
const newarray = item.incorrectAnswers
return {
options: newarray,
question: item.question,
correctAnswer: item.correctAnswer,
}
})
setQuestionsAre(transformData)
} catch (err) {
console.log(err, 'err in getting data')
}
}
QuizFetch()
}, [])
return (
<div className="Layout">
<Header />
<ProgressBar
changeCurrentQuestion={changeCurrentQuestion}
currentQuestion={currentQuestion}
questionAre={questionAre}
/>
{/* <QuizMain
changeCurrentQuestion={changeCurrentQuestion}
currentQuestion={currentQuestion}
questionAre={questionAre} /> */}
<Outlet context={[changeCurrentQuestion, currentQuestion, questionAre]} />
</div>
)
}
export default Layout
Since react 18 and the lifecycle in dev mode you have to use the abortController.
The signal will jump to the catch and then you will only have one successfull api call
useEffect(() => {
const abortController = new AbortController();
const QuizFetch = async () => {
try {
const res = await fetch(
'https://the-trivia-api.com/api/questions?categories=food_and_drink,general_knowledge&limit=10®ion=AU&difficulty=easy',
{
signal: abortController.signal,
},
)
const data = await res.json()
const transformData = data.map((item) => {
const newarray = item.incorrectAnswers
return {
options: newarray,
question: item.question,
correctAnswer: item.correctAnswer,
}
})
setQuestionsAre(transformData)
} catch (err) {
if (abortController.signal.aborted) return;
console.log(err, 'err in getting data')
}
}
QuizFetch()
return () => {
abortController.abort();
};
}, [])
const API_URL = 'http://www.omdbapi.com/?i=tt3896198&apikey=caca2ca6';
const App = () => {
const searchMovies = async (title) => {
const response = await fetch(`${API_URL}&s=${title}`);
const data = await response.json();
console.log(data.Search);
}
useEffect=(() => {
searchMovies('Batman');
}, []);
return(
<h1>Apppi</h1>
);
}
export default App;
the code above should show the movies title in the api on console, the tutorial im watching has title showed up. Can someone solve this for me? Thanks
there is syntax error in useEffct
remove = between useEffect and parentheses
useEffect(() => {
searchMovies('Batman');
}, []);
please always call the API in try-catch, this way your app will not crash and you may log error and you have to remove the = in useEffect before paranthesis.
const API_URL = 'http://www.omdbapi.com/?i=tt3896198&apikey=caca2ca6';
const App = () => {
const searchMovies = async (title) => {
try {
const response = await fetch(`${API_URL}&s=${title}`);
const data = await response.json();
console.log(data.Search);
} catch (error) {
console.log("error", error)
}
}
useEffect(() => {
searchMovies('Batman');
}, []);
return(
<h1>Apppi</h1>
);
}
export default App;
I am using useEffect to get data from an api.
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
`/api/posts/getCats`
);
const cats = await response.json();
console.log(cats);
} catch (e) {
console.error(e);
}
};
fetchData();
}, []);
The problem is when I try to use it in the return, its value is undefined.
{cats.map((data) => {
cats has value when I console.log it.
I cannot use componentDidMount because all my code is functional components.
Edit: I updated the code as per answers below but still get
TypeError: cats.map is not a function
All answers below actually make sense but I am not sure why its not working.
export default function Posts() {
const [cats, setCats] = useState();
useEffect(() => {
fetch(`/api/posts/getCats`)
.then(res => res.json())
.then(setCats)
.catch(console.error);
}, []);
return (
<div>
{cats?.map((data) => {
<h4>{data.main}</h4>
})}
</div>
)
}
This is because React renders your screen before finishing to get response from API. When you render screen, variable cats doesn't have values. You can run useEffect after each rendering. You can rerender by changing state from useEffect (This technique is often used). Do not forget to add [] or [cats] as a dependency of useEffect (second params) otherwise you will get infinite loop.
Below code works even when cats === [] or some array.
export default () => {
const [cats, setCats] = useState([])
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
`/api/posts/getCats`
);
const result = await response.json();
setCats(result)
} catch (e) {
}
};
fetchData();
}, []);
return (
<div>
{cats.map(cat => <div>cat</div>)}
</div>)
}
You have to map the cats data into state.
const [cats, setCats] = useState([]);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
`/api/posts/getCats`
);
const data = await response.json();
setCats(data);
} catch (e) {
console.error(e);
}
};
fetchData();
}, []);
You need to
call setCats when the response comes back (right now, you're just logging it)
.map only once cats has been populated:
const [cats, setCats] = useState();
useEffect(() => {
fetch(`/api/posts/getCats`)
.then(res => res.json())
.then(result => setCats(result.cats))
.catch(console.error);
}, []);
return (
<div>
{cats?.map((data) => {
// ...
I am executing useEffect() to update a state with JSON data. However the fetch request sometimes fails, so I want to re-execute the useEffect hook if that happens:
...
import React, {useState, useEffect} from 'react';
import {getJsonData} from './getJsonData';
const myApp = () => {
var ErrorFetchedChecker = false;
const [isLoading,setIsLoading] = useState(true);
const [data,setData] = useState(null);
const updateState = jsonData => {
setIsloading(false);
setData(jsonData);
};
useEffect(() => {
//console.log('EXECUTING');
getJsonData().then(
data => updateState(data),
error => {
Alert.alert('DATA FETCHING ERROR !', 'Refreshing ?...');
ErrorFetchedChecker = !ErrorFetchedChecker;
//console.log('LOG__FROM_CountriesTable: Executed');
},
);
}, [ErrorFetchedChecker]);//Shouldn't the change on this variable
//be enough to re-execute the hook ?
return (
<View>
<Text>{state.data.title}</Text>
<Text>{data.data.completed}</Text>
</View>
);
}
Here's the getJsonData() function just in case:
export async function getJsonData() {
try {
let response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
let responseJson = await response.json();
return responseJson;
} catch (error) {
throw error;
// Also, is this the correct way to handle the error ?
// As the Alert in useEffect goes off either ways.
// If not, advise me on how the error should be handled.
}
}
This will work
const myApp = () => {
const [errorFetchedChecker, setErrorFetchedChecker] = useState(false);
const [isLoading,setIsLoading] = useState(true);
const [data,setData] = useState(null);
const updateState = jsonData => {
setIsloading(false);
setData(jsonData);
};
useEffect(() => {
//console.log('EXECUTING');
getJsonData().then(
data => updateState(data),
error => {
Alert.alert('DATA FETCHING ERROR !', 'Refreshing ?...');
setErrorFetchedChecker(c => !c);
//console.log('LOG__FROM_CountriesTable: Executed');
},
);
}, [errorFetchedChecker]);
return (
<View>
<Text>{state.data.title}</Text>
<Text>{data.data.completed}</Text>
</View>
);
}
import React, { useState, useRef, useEffect } from "react";
import { Text, View, TextInput } from "react-native";
const App = () => {
var ErrorFetchedChecker = false;
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState(null);
const updateState = (jsonData) => {
setIsLoading(false);
setData(jsonData);
};
useEffect(() => {
//console.log('EXECUTING');
getJsonData()
.then((data) => {
console.log("1. Successful, just received the data from our promise");
updateState(data);
console.log("2. We set our data because we received it successfully");
return { alreadySet: true };
})
.catch((e) => {
console.log("1. We failed to gather data in our initial promise");
console.log("2. Attempting to rerun initial promise");
return getJsonData();
})
.then((data) => {
if (data.alreadySet) {
console.log(
"3. Did not attempt to retry because we are already successful"
);
} else {
console.log("3. Our second attempt succeeded");
updateState(data);
console.log("4. Set our data on our second attempt");
}
})
.catch((e) => {
console.log("3. Both attempts have failed");
});
}, []); //Shouldn't the change on this variable
//be enough to re-execute the hook ?
return (
<View>
<Text>{data ? <Text>{data.title}</Text> : null}</Text>
</View>
);
};
export async function getJsonData() {
try {
let response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
let responseJson = await response.json();
return responseJson;
} catch (error) {
throw error;
// Also, is this the correct way to handle the error ?
// As the Alert in useEffect goes off either ways.
// If not, advise me on how the error should be handled.
}
}
export default App;