Multiple nested axios calls don't resolve as expected - reactjs

As described in comments between my code snippet, the asynchronicity is not working as expected. For each id, an object/item should return but it only returns one item since my async await isn't implemented properly. What could be a possible workaround?
Thanks in advance
useEffect(() => {
axios.get('url-here').then((res) => {
res.data.favProperties?.map((el) => {
console.log(el) // this returns multitple id's of saved/liked items
axios.get('url-here').then(async (r) => {
if (r.data) {
console.log(r.data) // Problem starts here
// This returns the full object of the liked items
// But only one object is returned, not every object for which an id was stored
await storageRef
.child(r.data.firebaseRef + '/' + r.data.images[0])
.getDownloadURL()
.then((url) => {
// Here i need to fetch the image for each object
console.log(url)
})
.catch((err) => console.log(err))
}
})
})
})
}, [])

I think breaking down your operations into functions will prevent this Promise Hell. I would recommend using async await for these kinda operations. Also I was confused about the last part of console logging the download URL, by my guess you're trying to save all the download URLs for these liked items in an array.
useEffect(() => {
firstFunction();
}, []);
const firstFunction = async () => {
const { data } = await axios.get("url-here");
const favProperties = data.favProperties;
const fetchedUrls = await Promise.all(
favProperties?.map(async (el) => (
await secondFunction(el.id) /** use el to pass some ID */
))
);
};
const secondFunction = async (someId) => {
/** your second URL must point to some ID (or some parameters) specific API otherwise
running same op in a loop without variations doesn't make any sense */
const { data } = await axios.get(`some-other-url/${someId}`);
if (data) {
console.log(data);
const fetchedUrl = await storageThing(data);
return fetchedUrl;
}
};
const storageThing = async ({ firebaseRef, images }) => {
try {
const downloadURL = await storageRef
.child(firebaseRef + "/" + images[0])
.getDownloadURL();
console.log(downloadURL);
return downloadURL;
} catch (error) {
console.log(error);
return '';
}
};

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

Why Promise.all is not getting values?

I am using React and inside useEffect I am pulling data but I am getting an emtpy array after all the promises are resolved and cannot figure out why.
Here is the code:
const data = mainnet.FairLaunch.pools.map((pool) => {
const loadingStakingData = async () => {
const stakedValue = await getStakeValue(pool);
const poolDaily = await getPoolDaily(pool);
console.log( { stakedValue, poolDaily }) // all good here and printing what it has to print
return { stakedValue, poolDaily };
};
return loadingStakingData();
});
Promise.all(data).then((values) => {
console.log('value', values) // not logging anything here
setStakingData(values);
}); // always an empty array
Any idea why this is happening?
You might be returning a pending promise in the map function. Maybe try this code instead to return the value
const data = mainnet.FairLaunch.pools.map(async (pool) => {
const stakedValue = await getStakeValue(pool);
const poolDaily = await getPoolDaily(pool);
console.log( { stakedValue, poolDaily }) // all good here and printing what it has to print
return { stakedValue, poolDaily };
});
Promise.all(data)
.then((values) => {
console.log('value', values)
setStakingData(values);
})
.catch(console.error) // Remember to catch errors!!!

how to refactor duplicate API calls into a single API call?

I am pretty new to building full-stack applications, and I could like to avoid duplicating code in order to build the following to perform the calls in react my endpoints can be called like the following /api/v1/feeds/list/?page=${page} or api/v1/feeds/list/?search=${query} , but I would like to joing ?page=${page}&?search=${query} since search param is optional . I just want to make a single api call
async function fetchFeed(page) {
return api.get(`http://localhost:8001/api/v1/feeds/list/?page=${page}`);
}
async function searchQuery(query) {
return api.get(`http://localhost:8001/api/v1/feeds/list/?search=${query}`);
}
const Main = () => {
const [currentPage, setCurrentPage] = useState(1);
const [feed, setFeed] = useState([]);
const [feedCount, setfeedCount] = useState(0);
const [visible, setVisible] = useState(3)
const showMoreItems = () => {
setVisible(prevValue => prevValue + 3);
}
const browse = (page) => {
fetchFeed(page)
.then(function(response){
setfeedCount(response.data.count)
setFeed(response.data.results)
})
.catch(function(error){
console.log(error);
});
}
// fetches data
const fetchData = (search) => {
searchQuery(search)
.then((response) => {
setFeed(response.data.results)
})
.catch((error) => {
console.log(error);
});
};
const handleSearch = (e) =>{
fetchData(e.target.value);
}
useEffect(() => {
browse(currentPage)
fetchData(feed);
}, [currentPage]);
}
I'd pass an object with both page and query, which both default to the empty string - and if empty, don't include them in the fetched URL:
async function fetchFeed({ page = '', query = '' }) {
return api.get(`http://localhost:8001/api/v1/feeds/list/?${page ? `page=${page}&` : ''}${query ? `search=${query}` : ''}`);
}
If possible, make your API accept empty query parameters too, allowing you to simplify to
return api.get(`http://localhost:8001/api/v1/feeds/list/?page=${page}&query=${query}`);
Something like this should work for you
const fetchFeed = async (page, query) => {
let url =`http://localhost:8001/api/v1/feeds/list/?page=${page}`
if(query) url += `?search=${query}`
return api.get(url)
}
const browse = (page search) => {
await fetchFeed(page search)
.then(function(response){
!search && setfeedCount(response.data.count)
setFeed(response.data.results)
})
.catch(function(error){
console.log(error);
});
}
useEffect(() => {
browse(currentPage) // just pass page
browse(currentPage, searchQuery); // pass both page and search query
}, [currentPage]);

React Native - state is returning null after setting state

I'm very much new to react native currently i'm building small app for just getting an idea about this. I'm facing an issue in mapping the data from API. This is the json response returning from the api
{"data":[{"digit":300,"countsum":"52"},{"digit":301,"countsum":"102"},{"digit":302,"countsum":"27"},{"digit":303,"countsum":"201"},{"digit":500,"countsum":"101"}]}
When i tried to map this data i'm facing some issues. I stored the response from API to the state and when i tried to display the state data using map function it's showing the state value is null. This the code i tried till now
const [listdata, setListData] = useState(null)
useEffect(() => {
// Run! Like go get some data from an API.
getListData();
}, []);
const getListData = async () => {
const token = await AsyncStorage.getItem("#userToken")
axios
.get(constants.BASE_URL + "getlist?token=" +token)
.then(response => setListData(response.data))
.catch(error => {
console.log(error);
});
listdata.map(item => <Text>{item.digit}</Text>)
}
Do it like this,
export default function ComponentName () {
const [listdata, setListData] = useState([])
useEffect(() => {
// Run! Like go get some data from an API.
getListData();
}, []);
const getListData = async () => {
const token = await AsyncStorage.getItem("#userToken")
axios
.get(constants.BASE_URL + "getlist?token=" +token)
.then(response => setListData(response.data))
.catch(error => {
console.log(error);
});
}
return (<>
listdata.map(item => <Text>{item.digit}</Text>)
</>
);
}
You have to wait the fetch execution and later do the list map.
// wait for it
await axios
.get(constants.BASE_URL + "getlist?token=" +token)
.then(response => setListData(response.data))
.catch(error => {
console.log(error);
});
listdata.map(item => <Text>{item.digit}</Text>)
If you want to map the data then do that inside return statement of your code ,like:
return(
{listData?listdata.map(item => return <Text>{item.digit}</Text>):""}
);
This is a sample of a meant in my comment above:
Try console.log listdata at this stage, you will find that it is still
null, in other words, the value of the updated value of the
listdata:useSate will be ready after the render take place. You can
make another function outside of the current one. then use useEffect
with listdata to update your text views
const [listdata, setListData] = useState(null)
useEffect(() => makeRemoteRequest(), [listdata])
makeRemoteRequest = () => {
const url = `your-url-of-data-here`;
fetch(url)
.then(res => res.json())
.then(res => {
setListData(res.data);
})
.catch(error => {
console.log(error)
});
};
You could try the following:
const [listdata, setListData] = useState([])
useEffect(() => {
// Run! Like go get some data from an API.
getListData();
}, []);
const getListData = async () => {
const token = await AsyncStorage.getItem("#userToken")
try {
const dataResponse = await axios.get(constants.BASE_URL + "getlist?token=" +token);
setListData(dataResponse.data || [] );
} catch(error) {
console.log(error);
}
}
return (<>
listdata.map(item => <Text>{item.digit}</Text>)
</>);

How to ensure API data is called and added with .then

I am getting data in useEffect and looping through it to add additional data.
I want to do some calculations on it after all data being added to results, I make the calculations inside if (response.data.next) but after that inside then when I try to access data it prints old data.
How can I make make sure all data added then be able to use it in then?
const [results, setResults] = useState([]);
useEffect(() => {
async function handleAPIRequest(url) {
return await axios
.get(url)
.then(async (response) => {
await setResults((results) => [...results, ...response.data.results]);
if (response.data.next) {
await handleAPIRequest(response.data.next);
}
return results;
})
.then(async () => {
// this is where I want to use results
console.log("resultss: ", results);
});
}
handleAPIRequest(url)
}, []);
I would suggest to keep consistency between async/await or chaining promises. your approach can cause multiple setResults, not sure if that's what you desire. Below, I offer a solution that might suit your needs:
useEffect(() => {
async function handleAPIRequest(url, currentResults = []) {
try {
const response = await axios.get(url)
const nextResults = [...currentResults, ...response.data.results];
if (response.data.next) {
return await handleAPIRequest(response.data.next, nextResults);
}
return nextResults;
} catch (error) {
throw error;
}
}
try {
const finalResults = await handleAPIRequest(url);
setResults(results => [...results, ...finalResults]);
} catch (error) {
// here you can handle error response
console.log(error);
}
}, []);
// to do something after results state is updated use another use effect to accomplish that
useEffect(() => {
// do something on updated results state
}, [JSON.stringify(results)]);
You should change your code in the following way:
const [results, setResults] = useState([]);
useEffect(() => {
const handleAPIRequest = async url => {
const lastResult = await axios.get(url);
setResults([...results, ...lastResult.data.results]);
const toLogResults = [...results, ...lastResult.data.results];
if(response.data.next) {
await handleAPIRequest(response.data.next);
} else {
console.log("The final results are", toLogResults);
}
}
handleAPIRequest(url)
}, []);
The main problem is that results in lines following setResults() are not updated immediately, so the value is the old one.
The toLogResults is used just to display the actual result, it is not needed.

Resources