Async .map() not working within React hook - reactjs

I cannot understand why the .map() function is somehow not being called in this function. I'm trying to call my API asynchronously within the componentDidMount() hook, but it seems that the array method never gets called.
async componentDidMount() {
try {
const response = await fetch(url);
const data = await response.json();
console.log(`Returned: ${data.zipCodes.length} results`);
console.log(data.zipCodes[0]);
const geoPoints = data.zipCodes;
geoPoints
.filter(geoPoint => geoPoint.Recipient_Postal_Code !== "")
.map(function (geoPoint) {
const location = zipToLocation.find(element => element.fields.zip === geoPoint.Recipient_Postal_Code);
if (!location){
return;
}
if(!location.hasOwnProperty('fields')) {
return;
}
const lat = location.fields.latitude;
const lng = location.fields.longitude;
const result = {
lat: lat,
lng: lng,
trackingNumber: geoPoint.Shipment_Tracking_Number
}
console.log(result); //OK!
return result;
});
console.log(`Mapped ${geoPoints.length} geoPoints`);
console.log(geoPoints); //same data as data.zipCodes ??
this.setState({data: geoPoints})
} catch(error) {
console.log(error);
}
}
Is is somehow being disrupted by React?

.map doesn't edit the input array, it returns a new array that is created from the old one.
You want to put:
const geoPoints = data.zipCodes
.filter(...)
.map(...);

Related

How to check if a certain key name exist in json object in React app

I create this custom hook in my React app. It should return a boolean.
const useFetchResponse = (url: string) => {
const [isValid, setIsValid] = useState<boolean>(false);
useEffect(() => {
const fetchResponse = async () => {
const response = await fetch(url);
console.log(response);
const obj = await response.json();
if (response.ok) {
console.log(await response.json());
setIsValid(true);
}
return response;
};
fetchResponse().then((res) => res);
}, []);
return isValid;
};
export default useFetchResponse;
When I log const obj = await response.json(); it returns: {"keyName":"some=key"}.
How do I create a condition to check if response.json() has a key named keyName?
Is that for example console.log('keyName' in obj) // true?
Do you see more things which I can improve and refactor?
Let assume you get response as follow
let response = {
a:'data1',
b:'data2',
c:'data3'
};
Then you can extract keys from object as below:
let keyOnly = Object.keys(response)); // output will be ["a","b","c"]
then you can check if your require value includes on above array or not as below: Assuming if you want to check if "b" is included or not
let checkKey = keyOnly.includes(b)
if you want to check whether an object has a certain property or not, the in operator is fine.
const obj = { a: 1 };
'a' in obj // return true
'b' in obj // return false
About improvements
it's better to save all fetch states, not only valid or not. And you should wrap request with try/catch block. For example:
const [fetchState, setFetchState] = useState('pending');
useEffect(() => {
const fetchResponse = async () => {
try {
setFetchState('loading');
const response = await fetch(url);
console.log(response);
const obj = await response.json();
if (response.ok) {
console.log(await response.json());
setFetchState('success');
}
return response;
} catch (error) {
setFetchState('failed')
}
};
fetchResponse().then((res) => res);
}, []);
return fetchState;
};
fetchResponse(); would be enough. fetchResponse().then((res) => res); is unnecessary.
[optional] You could use libraries to making requests, like an axios. That would be more convenient.
in is slower than below way.
const isValid = obj[`keyname`] !== undefined
Check more detail in here

Nested axios API call causes trouble on updating state React

I'm trying to build a simple app that fetches data from an API and shows them. I have a scenario in which I have to fetch the IDs of some items, and then for every ID make an API call to get the details. I want to set the array of fetched details as a state, and I can actually do it, but the view does not update and I don't understand why... I guess I'm doing a mess with asynchronous calls, as always...
updateSearchResults() is a state setter passed as a prop from the upper level component, and the holder of the state is that same upper level component.
async function handleSubmit(event) {
event.preventDefault();
if(name) {
let res = await axios.get(`https://www.foo.bar/search`);
if(res.data.items !== null) {
const filteredItems = filterItems(res.data.items);
updateSearchResults(filteredItems); //this works as expected
}
} else {
let res = await axios.get(`https://www.foo.bar/anothersearch`);
if(res.data.items !== null) {
let items= [];
res.data.items.forEach(async item => {
const resDetails = await axios.get(`https://www.foo.bar/getdetails`);
items.push(resDetails.data.items[0]);
})
console.log(items); //this prints the expected result
updateSearchResults(items); //this updates the state but not the view
}
}
}
...
const items= await Promise.all(res.data.items.map(async item => {
const resDetails = await axios.get(`https://www.foo.bar/getdetails`);
return resDetails.data.items[0];
}));
console.log(items); //this prints the expected result
updateSearchResults(items);
You can modify your code to something like this:
async function handleSubmit(event) {
event.preventDefault();
if(name) {
let res = await axios.get(`https://www.foo.bar/search`);
if(res.data.items !== null) {
const filteredItems = filterItems(res.data.items);
updateSearchResults(filteredItems); //this works as expected
}
} else {
let res = await axios.get(`https://www.foo.bar/anothersearch`);
if(res.data.items !== null) {
let items= [];
for await (const item of res.data.items) {
const resDetails = await axios.get(`https://www.foo.bar/getdetails`);
items.push(resDetails.data.items[0]);
}
console.log(items); //this prints the expected result
updateSearchResults(items); //this updates the state but not the view
}
}
}

ReactJs Unable to setSate in componentDidMount from async function

I'm calling an async function (getData()) in componentDidMount, and I'm trying to use this.setState with result of that function.
componentDidMount() {
let newData = getData();
newPodData.then(function (result) {
console.log('result', result)
this.setState({result})
})
}
However, I'm having issues getting my state to properly update. Some additional context - I'm trying to set my initial state with data I am receiving from a database. Is my current approach correct? What's the best way to accomplish this? Here's my async function for more context:
const getTeamData = async () => {
const getTeamMembers = async () => {
let res = await teamMemberService.getTeamMembers().then(token => { return token });
return res;
}
const getActiveTeams = async () => {
let res = await teamService.getActiveTeams().then(token => { return token });
return res;
}
const teamMemberResult = await getTeamMembers()
const activeTeamsResult = await getActiveTeams();
// get team member data and add to teamMember object
let teamMemberData = teamMemberResult.reduce((acc, curr) => {
acc.teamMembers[curr.id] = curr;
return acc;
}, {
teamMembers: {}
});
// get team ids and add to teamOrder array
let activeTeamsData = activeTeamsResult.map(team => team.id)
let key = 'teamOrder'
let obj = []
obj[key] = activeTeamsData;
const newObject = Object.assign(teamMemberData, obj)
return newObject;
}
export default getTeamData;
Changing the function inside the then handler to an arrow function should fix it. e.g:
componentDidMount() {
let newData = getData();
newPodData.then((result) => {
console.log('result', result)
this.setState({result})
})
}
But I'll like to suggest a better way to write that.
async componentDidMount() {
let result = await getData();
this.setState({result})
}

ReactJS - commands do not run in desired order

I am trying to build a find nearby restaurants app in ReactJS and I am having a problem with displaying array data on the first run of this command below.
What happens is that the first time I fetch data with axios it only load location. The second time I fetch data it also renders the array data on the interface.
I think the problem is with the order of the command I run in this function and on the first run it tries to fetch undefined lat & lng, then after the second time we run it in the same session it renders based on what lat & lng was fetches with the previous run of the function.
Here's the code:
currentLocationOnClick = async () => {
let { lat, lng } = this.state;
const URL = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${lat},${lng}&type=restaurant&radius=${5 *
1000}&key=AIzaSyBpd_v1C8RFh0D39Al97ANZ-eJLO3zrKAQ`;
navigator.geolocation.getCurrentPosition(
position => {
this.setState({ lat: position.coords.latitude });
this.setState({ lng: position.coords.longitude });
},
error => {
console.log('Error getting location');
}
);
let places;
try {
const response = await axios.get(URL);
console.log(response.data);
places = response.data.results;
} catch (error) {
console.log(error.message);
}
this.setState({ places });
};
navigator.geolocation.getCurrentPosition is an async function, meaning the function call does not block until it finished.
one way of fixing this would be to put your axios call in position callback.
For example, something like this:
async position => {
const URL = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${position.coords.latitude},${position.coords.longitude}&type=restaurant&radius=${5 *
1000}&key=AIzaSyBpd_v1C8RFh0D39Al97ANZ-eJLO3zrKAQ`;
const response = await axios.get(URL);
console.log(response.data);
places = response.data.results;
this.setState({ places });
},
Another nicer, solution would be to wrap navigator.geolocation.getCurrentPosition so it's an awaitable function.
something like this:
function getCurrentPosition()
{
return new Promise(resolve => navigator.geolocation.getCurrentPosition(position => resolve(position)))
}
Then you could use it something like this:
let position = await getCurrentPosition();
const URL = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${position.coords.latitude},${position.coords.longitude}&type=restaurant&radius=${5 *
1000}&key=AIzaSyBpd_v1C8RFh0D39Al97ANZ-eJLO3zrKAQ`;
const response = await axios.get(URL);

React - How do I get fetched data outside of an async function?

I'm trying to get the data of "body" outside of the fetchUserData() function.
I just want to store it in an variable for later use.
Also tried modifying state, but didn't work either.
Thanks for your help :)
const [userData, setUserData] = useState();
async function fetchUserData () {
try {
const result = await fetch(`/usermanagement/getdocent`, {
method: "GET"
});
const body = await result.json();
//setUserData(body);
return(
body
)
} catch (err) {
console.log(err);
}
}
let userTestData
fetchUserData().then(data => {userTestData = data});
console.log(userTestData);
//console.log(userData);
Use useEffect
async function fetchUserData () {
try {
const result = await fetch(`/usermanagement/getdocent`, {
method: "GET"
})
return await result.json()
} catch (err) {
console.log(err)
return null
}
}
const FunctionalComponent = () => {
const [userData, setUserData] = useState()
useEffect(() => {
fetchUserData().then(data => {
data && setUserData(data)
})
}, []) // componentDidMount
return <div />
}
Ben Awad's awesome tutorial
Example:
it seems that you are making it more complicated than it should be. When you get the response i.e the resolved promise with the data inside the async function, just set the state and in the next render you should get the updated data.
Example:
const [userData, setUserData] = useState();
useEffect(() => {
const getResponse = async () => {
try {
const result = await fetch(`/usermanagement/getdocent`, {
method: "GET"
});
const body = await result.json();
setUserData(body);
} catch (err) {
console.log(err)
}
}
getResponse();
}, [])
console.log(userData);
return <div></div>
Assuming the you need to call the function only once define and call it inside a useEffect or 'componentDidMount'. For using async function inside useEffect we need to define another function and then call it.
When you do
let userTestData
// This line does not wait and next line is executed immediately before userTestData is set
fetchUserData().then(data => {userTestData = data});
console.log(userTestData);
// Try changing to
async someAsyncScope() {
const userTestData = await fetchUserData();
console.log(userTestData)
}
Example:
state = {
someKey: 'someInitialValue'
};
async myAsyncMethod() {
const myAsyncValue = await anotherAsyncMethod();
this.setState({ someKey: myAsyncValue });
}
/*
* Then in the template or where ever, use a state variable which you update when
* the promise resolves. When a state value is used, once the state is updated,
* it triggers as a re-render
*/
render() {
return <div>{this.state.someKey}</div>;
}
In your example you'd use setUserData instead of this.setState and userData instead of {this.state.someKey}

Resources