I'm fetching an api and want to change useState when the api is returned. However, it simply isn't updating.
Any suggestions?
const fictionApi = "http://localhost:3000/fiction"
const nonFictionApi = "http://localhost:3000/non_fiction"
const [fictionData, setFictionData] = useState(null)
const db = async (url) => {
const res = await fetch(url)
const data = await res.json()
setFictionData(data)
console.log(data.genre)
}
useEffect(() => {
const db = async (url) => {
const res = await fetch(url)
const data = await res.json()
setFictionData(data)
console.log(fictionData)
}
db(fictionApi)
}, [])
I think there is something strange with your syntax.
Something like this should work :
const fictionApi = "http://localhost:3000/fiction"
const nonFictionApi = "http://localhost:3000/non_fiction"
export default function Page () {
const [fictionData, setFictionData] = useState(null);
const [url, setUrl] = useState(fictionApi); // ';' very important
useEffect(() => {
(async () => {
const res = await fetch(url)
const data = await res.json()
setFictionData(data)
})() //Self calling async function
}, [])
}
Moreover, setState is an async process so :
const [fictionData, setFictionData] = useState(null);
setFictionData(true)
console.log(fictionData) //null
So you can use a useEffect to check state :
const [fictionData, setFictionData] = useState(null);
useEffect(()=>{
console.log(fictionData) //true
},[fictionData])
setFictionData(true)
Related
I have three apis in all. GetAssets is the first, followed by assetsOptionsList and getAssetsLibrary. The issue I'm having is that when I post the data on getAssetsLibrary, I want to be able to present it on get Assets at runtime.Everything is working fine but i want to show assets on runtime.
I'm setting the runTime state true on get request but the problem is it works only for one time.Second time, it does not map on runtime. Actually, i want to know is there any alternative so that i can achieve the goal.
In the below code the one function is getting the assets. And i want to run the one function when the post request successfully sent.
const [images, setImages] = useState([]);
const [assetOptions, setAssetOptions] = useState([]);
const [faqOpened, setToggleFaq] = useState(false);
const [runTime, setRunTime] = useState(false)
const [assetID, setAssetID] = useState()
const [isLoading, setIsLoading] = useState(false);
const handleForm = (e) => {
const index = e.target.selectedIndex;
const el = e.target.childNodes[index]
const option = el.getAttribute('id');
setAssetID(option)
}
const formHandler = (e) => {
e.preventDefault()
let formData = new FormData();
formData.append('media', e.target.media.files[0]);
formData.append('assetListId', assetID)
formData.append('name', e.target.name.value);
console.log(Object.fromEntries(formData))
const res = axios.post('api/asset-library',
formData
).then((response) => {
showSuccessToaster(response?.data?.message)
setRunTime(true)
setToggleFaq(false)
})
.catch((error) => {
showErrorToaster(error?.response?.data?.message)
})
}
const showSuccessToaster = (response) => {
return uploadToasterSuccess.show({ message: response });
}
const showErrorToaster = (error) => {
return uploadToasterError.show({ message: error });
}
const one = async () => {
setIsLoading(true)
const data = await axios.get('api/assets').then((res) => {
return res?.data?.data
})
setImages(data)
setIsLoading(false)
}
const two = async () => {
const data = await axios.get('/api/asset-list').then((res) => {
return res?.data?.data
})
setAssetOptions(data)
}
useEffect(() => {
one()
two()
}, [runTime]);
I have this Firebase Firestore query set up in my useEffect hook:
const [favorites, setFavorites] = useState([]);
const { user, setUser } = useContext(AuthenticatedUserContext);
const getUserFavorites = async () => {
const favoritesRef = collection(db, "favorites");
const q = query(favoritesRef, where("userId", "==", user.uid));
const querySnapshot = await getDocs(q);
const fetchedFavorites = [];
querySnapshot.forEach(async (document) => {
const docRef = doc(db, "receipes", document.data().recipeId);
const docSnap = await getDoc(docRef);
const data = docSnap.data();
fetchedFavorites.push(data);
});
setFavorites(fetchedFavorites);
console.log("favorites " + favorites);
};
useEffect(() => {
getUserFavorites();
}, []);
Upon first render of the page the favorites will be [] and after a re-render it will be populated. When logging within the forEach I can see that the query is working, so I suspect the async forEach being the culprit here. How can I fix this?
Yes, you should not use async function with a forEach() loop. You can use a for-of loop with the same code or use Promise.all() as shown below:
const getUserFavorites = async () => {
const favoritesRef = collection(db, "favorites");
const q = query(favoritesRef, where("userId", "==", user.uid));
const querySnapshot = await getDocs(q);
const favPromises = querySnapshot.docs.map((d) => getDoc(doc(db, "receipes", d.data().recipeId)))
const fetchedFavs = (await Promise.all(favPromises)).map((fav) => fav.data());
setFavorites(fetchedFavs);
console.log("favorites " + fetchedFavs);
};
Also checkout: Using async/await with a forEach loop for more information.
I want to fetch api and set state, then fetch another api with the state that comes from first one. So is that possible ?
Here is my code that i struggle with ;
useEffect(()=>{
const fetchPokemonSpecies = async (pokemon) => {
const data = await axios("https://pokeapi.co/api/v2/pokemon-species/"+pokeName).then( (response) =>
axios(response.data.evolution_chain.url)).then((response) => {setPokemonFirstEvolution(response.data.chain.evolves_to[0].species.name)
setPokemonFirstForm(response.data.chain.species.name)
setPokemonSecondEvolution(response.data.chain.evolves_to[0].evolves_to[0].species.name)})
setPokemonSpecies(data)
setPokemonCategory(data.genera[7].genus)
}
Thanks already !
So i just solve my problem this way ;
const [pokemonCategory, setPokemonCategory] = useState([])
const [pokemonSecondEvolution, setPokemonSecondEvolution] = useState([])
const [pokemonFirstEvolution, setPokemonFirstEvolution] = useState([])
const [pokemonFirstForm, setPokemonFirstForm] = useState([])
const [pokemonFirstFormIcon, setPokemonFirstFormIcon] = useState([])
const [pokemonFirstEvoIcon, setPokemonFirstEvoIcon] = useState([])
const [pokemonSecondEvoIcon, setPokemonSecondFormIcon] = useState([])
useEffect(()=>{
const fetchPokemonSpecies = async (pokemon) => {
const data = await axios("https://pokeapi.co/api/v2/pokemon-species/"+pokeName).then( (response) => response.data)
setPokemonSpecies(data)
setPokemonCategory(data.genera[7].genus)
const data2 = await axios(data.evolution_chain.url).then((response) => axios("https://pokeapi.co/api/v2/pokemon/"+response.data.chain.evolves_to[0].species.name))
setPokemonFirstEvoIcon(data2.data.sprites.other.dream_world.front_default)
const data3 = await axios(data.evolution_chain.url).then((response) => axios("https://pokeapi.co/api/v2/pokemon/"+response.data.chain.evolves_to[0].evolves_to[0].species.name))
setPokemonSecondFormIcon(data3.data.sprites.other.dream_world.front_default)
const data4 = await axios(data.evolution_chain.url).then((response) => axios("https://pokeapi.co/api/v2/pokemon/"+response.data.chain.species.name))
setPokemonFirstFormIcon(data4.data.sprites.other.dream_world.front_default)
}
fetchPokemonSpecies()
},[])
useEffect(()=>{
const fetchPokemonEvoChain = async (pokemon) => {
const data = await axios("https://pokeapi.co/api/v2/pokemon-species/"+pokeName).then( (response) =>
axios(response.data.evolution_chain.url)).then((response) => {setPokemonFirstEvolution(response.data.chain.evolves_to[0].species.name)
setPokemonFirstForm(response.data.chain.species.name)
setPokemonSecondEvolution(response.data.chain.evolves_to[0].evolves_to[0].species.name)})
}
fetchPokemonEvoChain()
},[])
idk if it is the optimal way but it works
Im sending 2 requests at the same time using useFetch, on Safari the responses are getting mixed
const [entries,setEntries] = useState([]);
const [categories,setCategories] = useState([]);
const { get, response} = useFetch('https://api.publicapis.org');
const getData = useCallback( async()=>{
const entriesRes = await get('/entries?category=animals&https=true')
if(response.ok)
setEntries(entriesRes.entries)
},[])
const getCategories = useCallback( async()=>{
const categoriesRes = await get('/categories')
if(response.ok)
setCategories(categoriesRes)
},[])
useEffect(()=>{
getData();
getCategories();
},[])
if the getCategories returns first the response go to entriesRes instand of categoriesRes this happens only on safari
Try renaming the useFetch results:
const [entries,setEntries] = useState([]);
const [categories,setCategories] = useState([]);
const categoryFetch = useFetch('https://api.publicapis.org');
const dataFetch = useFetch('https://api.publicapis.org');
const getData = useCallback( async()=>{
const entriesRes = await dataFetch.get('/entries?category=animals&https=true')
if(response.ok)
setEntries(entriesRes.entries)
},[])
const getCategories = useCallback( async()=>{
const categoriesRes = await categoryFetch.get('/categories')
if(response.ok)
setCategories(categoriesRes)
},[])
useEffect(()=>{
getData();
getCategories();
},[])
I have to functions getDataOne and getDataTwo. How do I combine below into one function, using fetch(), useState and useEffect?
const MyComponent = () => {
const [loading, setLoading] = useState(false);
const [dataOne, setDataOne] = useState<Data[]>([]);
const [dataTwo, setDataTwo] = useState<Data[]>([]);
const getDataOne = async () => {
setLoading(true);
const result = await fetch(
"https://my-api-link-one"
);
const jsonResult = await result.json();
setLoading(false);
setDataOne(jsonResult);
};
const getDataTwo = async () => {
setLoading(true);
const result = await fetch(
"https://my-api-link-two"
);
const jsonResult = await result.json();
setLoading(false);
setDataTwo(jsonResult);
};
useEffect(() => {
getDataOne();
getDataTwo();
}, []);
Update:
I set it up using Promise.all
const [loading, setLoading] = useState(false);
const [dataOne, setDataOne] = useState<DataOne[]>([]);
const [dataTwo, setDataTwo] = useState<DataTwo[]>([]);
const [data, setData] = useState<DataOne[] & DataTwo>([]);
const urls = [
"https://url-one", "https://url-two",
];
const getData = async () => {
setLoading(true);
const results = await Promise.all(
urls.map((url) => fetch(url).then((res) => res.json()))
);
setLoading(false);
setData(results);
console.log(data);
};
This is not totally working yet. How do I use useState now correctly (and handle both data from urls)? In the end I want to have one data variable so I can map over this variable:
{data.map((item) => {
return (
// etc
So, Promise.all() accepts an array of promises, so naturally Promise.all() returns an array only. So even though your results variable still is an array I would recommend destructuring it because in this case there are only two API fetches involved. Looking at your update, I think there's only small modifications left which are as follows :
const urls = ["https://url-one", "https://url-two",];
const getData = async () => {
setLoading(true);
const [result1, result2] = await Promise.all(
urls.map((url) => fetch(url).then((res) => res.json()))
);
setLoading(false);
setDataOne(result1);
setDataTwo(result2);
console.log(data);
};
You can use Promise.all. Read more here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all.
const getData = () => {
setLoading(true);
Promise.all([fetch('api-1'), fetch('api-2')]).then(results => {
setDataOne(results[0]);
setDataTwo(results[1]);
}).finally(() => setLoading(false));
}
Utilize .flat() to reformat the data array returned from the Promise.all() into your state which holds the response obj/array,
Promise.all(
urls.map(url =>
fetch(url).then(e => e.json())
)
).then(data => {
finalResultState = data.flat();
});