This question already has answers here:
Arrow function VS normal function in Array.map()
(2 answers)
Are 'Arrow Functions' and 'Functions' equivalent / interchangeable?
(4 answers)
Closed 12 months ago.
I'm pretty new to hooks and I'm trying to use setState for data I'm getting back from an API, but the state never updates. I only need to make the call when the component mounts, which is why the second argument in my useEffect is an empty array. I can see that I'm getting back the data when I console.log the response, it just isn't being set.
const [routeOptions, setRouteOptions] = useState()
useEffect(() => {
Axios.get("https://svc.metrotransit.org/NexTrip/Routes?format=json").then(response => {
const routes = response.data
setRouteOptions(routes)
});
}, []);
I then try to map through the data like so:
{routeOptions && routeOptions.map(option => {
<option>
{option.description}
</option>
})}
but because my state never got set there's nothing to map through.
I may be missing something super obvious cause I'm not familiar with hooks, so any help is appreciated!
You need to return a value from your .map(). Make sure to give your <option> a unique key prop as well.
Note also that the route property Description has a capital D :)
<select>
{routeOptions.map((option) => {
return <option key={option.Route}>{option.Description}</option>;
})}
</select>
here it is all together
import { useState, useEffect } from "react";
import Axios from "axios";
export default function Test() {
const [routeOptions, setRouteOptions] = useState(null);
useEffect(() => {
Axios.get("https://svc.metrotransit.org/NexTrip/Routes?format=json").then(
(response) => {
const routes = response.data;
setRouteOptions(routes);
}
);
}, []);
if (!routeOptions)
return (
<div>
<p>Loading...</p>
</div>
);
return (
<select>
{routeOptions.map((option) => {
return <option key={option.Route}>{option.Description}</option>;
})}
</select>
);
}
try to use Async-select https://react-select.com/async and make a function that returns an array of pair {label, value} instead of wasting your time stuck here.
Related
I don't understand why my "console.log(champion)" return nothing ...
Someone can explain me why the asynchrone function don't work ? Isn't setCahmp supposed to change the value of "champions"?
I guess it because axios take sometime to search datas... I don't know how I could fix it.
And then I would like to map "champion" but its an object, how I could do that ?
Thans you
import React, { useEffect, useState } from "react";
import axios from "axios";
const Champs = () => {
const [champions, SetChampions] = useState([]);
useEffect(() => {
axios
.get(
"http://ddragon.leagueoflegends.com/cdn/12.5.1/data/en_US/champion.json"
)
.then((res) => {
SetChampions(res.data.data);
console.log(res.data.data);
})
.then(
console.log(champions)
);
}, []);
return (
<div className="champs">
{/* {champions.map((champ) => {
return <p> {champ.id}</p>;
})} */}
</div>
);
};
export default Champs;
In your API response response.data.data is not an array of objects, it's nested objects and you are initializing the champions as an array. So, setChampions can't assign an object to an array.
Also, you can't use the map function to loop an object. You can use Object.keys to map the response.
You shouldn't do a double "then" on your code. If you want to know when the state champions is set you should use a second useEffect with "champions" in param :
useEffect(() => {
axios
.get(
"http://ddragon.leagueoflegends.com/cdn/12.5.1/data/en_US/champion.json"
)
.then((res) => {
SetChampions(res.data.data);
console.log(res.data.data);
});
}, []);
useEffect(() => {
console.log(champions)
}, [champions]);
If you want to map an object you should do this :
<div className="champs">
{Object.keys(champions).map((key) => {
const champ = champions[key]
return <p> {champ.id}</p>;
})}
</div>
Object.keys will return an array of key of your object, so you can map it. And to access to the value you can simply use the key like this : const champ = champions[key]
Hoping that can help you in your research
It could be that console.log(champion) isn't working because it's getting called before SetChampion is completed. You don't need the 2nd .then() call to check on champion. To make sure champion is getting set, you could make a useEffect that is called whenever champion gets set, like so:
useEffect(() => {
console.log(champion);
}, [champion])
This will get called when champion is initially set to [] with useState, and then when you set it with SetChampions().
I'm having an issue when trying to save to State an axios API call. I've tried
useState set method not reflecting change immediately 's answer and many other and I can't get the state saved. This is not a duplicate, because I've tried what the accepted answer is and the one below and it still doesn't work.
Here's the (rather simple) component. Any help will be appreciated
export const Home = () => {
const [widgets, setWidgets] = useState([]);
useEffect(() => {
axios
.get('/call-to-api')
.then((response) => {
const data = response.data;
console.log(data); // returns correctly filled array
setWidgets(widgets, data);
console.log(widgets); // returns '[]'
});
}, []); // If I set 'widgets' here, my endpoint gets spammed
return (
<Fragment>
{/* {widgets.map((widget) => { // commented because it fails
<div>{widget.name}</div>;
})} */}
</Fragment>
);
};
Welcome to stackoverflow, first thing first the setting call is incorrect you must use spread operator to combine to array into one so change it to setWidgets([...widgets, ...data]); would be correct (I assume both widgets and data are Array)
second, react state won't change synchronously
.then((response) => {
const data = response.data;
console.log(data); // returns correctly filled array
setWidgets(widgets, data);
console.log(widgets); // <--- this will output the old state since the setWidgets above won't do it's work till the next re-render
so in order to listen to the state change you must use useEffect hook
useEffect(() => {
console.log("Changed Widgets: ", widgets)
}, [widgets])
this will console log anytime widget changes
the complete code will look like this
export const Home = () => {
const [widgets, setWidgets] = useState([]);
useEffect(() => {
axios
.get('/call-to-api')
.then((response) => {
const data = response.data;
setWidgets([...widgets, ...data])
});
}, []);
useEffect(() => {
console.log("Changed Widgets: ", widgets)
}, [widgets])
return (
<Fragment>
{/* {widgets.map((widget) => { // commented because it fails
<div>{widget.name}</div>;
})} */}
</Fragment>
);
};
Try:
setWidgets(data);
istead of
setWidgets(widgets, data);
Your widgets.map() probably fails because there isn't much to map over when the component is being rendered.
You should update it with a conditional like so, just for clarity:
widgets.length>0 ? widgets.map(...) : <div>No results</div>
And your call to setWidgets() should only take one argument, the data:
setWidgets(data)
or if you want to merge the arrays use a spread operator (but then you need to add widgets as the dependency to the useEffect dependency array.
setWidgets(...widgets, ...data)
You might also have to supply the setWidgets hook function to the useEffect dependency array.
Let me know if this helps..
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 1 year ago.
Hi i am trying to show the data from a json url in ReactJS by inputing all the data to an array variable but i can't use the array in the JSX part because the array is not yet populated while rendering i try'd many things but i always end up in a promise loop where i need a promise to get the data from the other one.
THE CODE:
let arry = [];
let ar = [];
async function getdriver() {
const response = await fetch("https://ergast.com/api/f1/current/drivers.json");
ar = await response.json();
ar.MRData.DriverTable.Drivers.forEach((element) => {
arry.push(element);
});
return arry;
}
getdriver();
console.log(arry);// the array is populated but i think it waits for it before showing
console.log(arry.lenght); //lenght is 0
JSX:
return (
<div>
<Menu />
<div style={{ textAlign: "left" }}>
<h4>ff</h4>
<Button >change</Button>
<br></br>
<i>{arry[0].code}</i>// error ' Cannot read property 'code' of undefined ' so arry is empty?
</div>
</div>
);
Fetching data is a side-effect, and then you will need to store this data as state, so you will need to make use of two kinds of hooks (assuming you are creating function components):
useEffect
useState
Your asynchronous code will be called in useEffect, and when the call is completed you will save the result as the component's state using useState.
The code will look similar to the example below (I've kept as much as your code as possible, but renamed some functions and variables, and added some comments, to make this useful for as many other readers as possible):
import { useState, useEffect } from "react";
// this can exist outside the component
// it can even be in a different file
async function fetchDrivers() {
const response = await fetch(
"https://ergast.com/api/f1/current/drivers.json"
);
const data = await response.json();
return data.MRData.DriverTable.Drivers;
}
function YourComponent() {
// we declare the state, initially it's an empty array
const [drivers, setDrivers] = useState([]);
// we declare the effect that runs exactly once,
// when the component is mounted
useEffect(() => {
fetchDrivers().then(setDrivers);
}, []);
// we print the code for all drivers
// mapping the drivers array to JSX.
// notice the key attribute, this is required with map
// to uniquely identify each element
return (
<div>
<Menu />
<div style={{ textAlign: "left" }}>
<h4>ff</h4>
<Button>change</Button>
<br></br>
{drivers.map((driver, index) => (
<p key={index}>
<i>{driver.code}</i>
</p>
))}
</div>
</div>
);
}
When you want to show data that is fetched from a API on first render you should put the API call in useEffect and give an empty array as a dependency to useEffect while setting the array as a state value for example:
import {useState, useEffect} from 'React';
function YourComponent(){
const [array, setArray] = useState([]);
useEffect(()=>{getDriver().then((array)=>
{setArray(array)})}
,[])
}
this is just an example, in getDriver() after you get the result of the API call you should set array using setState() to tell React to re-render after that value was changed but here when you put it in useEffect it will only be triggered on first render.
This question already has answers here:
console log the state after using useState doesn't return the current value [duplicate]
(3 answers)
React hooks states aren't updating in log
(2 answers)
Closed 2 years ago.
I am trying to update a state after a trigger and want to use the state immediately for a function to trigger but the state is not updated immediately .
any suggestions ?
<Button
onClick={() => {
console.log(finalData) //old data
setFinalData(mutableData); // updating state
console.log(finalData) // still geeting old state
}>
</Button>
setFinalData is kind of async behaviour, so if you :
console.log(finalData) //old data
setFinalData(mutableData); // <----- async behavior
console.log(finalData) // you will never get updated finalData directly here
Solution : useEffect
useEffect(() => {
// you will get updated finalData here, each time it changes
console.log(finalData);
// you can trigger your function from here
},[finalData]);
Here is code snippet, hope this will clear your doubts :
const { useState , useEffect } = React;
const App = () => {
const [finalData,setFinalData] = useState([]);
useEffect(() => {
setTimeout(() => {
setFinalData([...finalData, "Vivek" , "Darshita"]);
console.log("finalData -----> not updated right after" ,finalData);
},2000);
},[]);
useEffect(() => {
if(finalData.length)
console.log("Final data updated , invoke your function" , finalData)
},[finalData]);
return (
<div>
{ finalData.map(user => <p>{user}</p>) }
</div>
);
}
ReactDOM.render(<App />, document.getElementById('react-root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react-root"></div>
This question already has answers here:
Where can I make API call with hooks in react?
(5 answers)
Closed 2 years ago.
I'm looking for feedback on this customer React hook. I'm wondering:
Does this look like a proper use of custom React hooks?
Is there a better way to switch between different API endpoints based upon the prop that is passed in? I'm looking to do something like:
<MovieGrid typeOfMovies={"popular"} />
and
<MovieGrid typeOfMovies={"upcoming"} />
Do you have any feedback or recommendations on anything you see. Thank you!
The code I've provided does indeed work. But since hooks a relatively new I don't feel totally confident I'm using them right.
Here's what I've got:
import React, { useState, useEffect } from "react";
function useFetchMovies(typeOfMovies) {
const [movieData, setMovieData] = useState([]);
const movieEndpointURL = () => {
if (typeOfMovies === "upcoming") {
return `https://api.themoviedb.org/3/movie/upcoming?api_key={API_KEY}&language=en-US&page=1®ion=US`;
} else if (typeOfMovies === "popular") {
return `https://api.themoviedb.org/3/movie/popular?api_key={API_KEY}&language=en-US&page=1®ion=US`;
}
};
const fetchMovieData = async () => {
try {
const res = await fetch(movieEndpointURL());
const movies = await res.json();
setMovieData(movies.results);
console.log(movies.results);
} catch (err) {
console.log(err);
}
};
useEffect(() => {
fetchMovieData();
}, []);
return [movieData, setMovieData];
}
export { useFetchMovies };
Your useFetchMovies seems to be correct expect the part that when typeOfMovies changes new data will not be fetched because when the useEffect first runs on mount of component it will refer to the fetchMoviesData that was initially created along with its closure and when the useFetchMovies hook is called again a new function is created which isn't referenced by the useEffect.
In order to make it word correctly you should pass typeOfMovies as the second argument to useEffect like
useEffect(() => {
fetchMovieData();
}, [typeOfMovies]);