Setting up Promise for two API calls in UseEffect - reactjs

const [activities, setActivities] = useState([""]);
const [contact, setContact] = useState();
const currentActivity = activities[0]
let contactId = currentActivity.contacts
useEffect(() => {
API.getActivities()
.then(res =>
setActivities(res.data)
).catch((err) => console.log(err))
API.getContact(contactId)
.then(res =>
setActivities(res.data)
).catch((err) => console.log(err))
}, []);
console.log(currentActivity)
console.log(contactId)
console.log(contact)
The first call sets the activity state which includes a contact id. I require the id to run the second call for the contact information. I believe I need to setup a promise but am getting stuck. When I run the code, the contact id does not return in time to pull the contact. Another solution could be to call all contacts and loop through to match the id returned by the contact. When I have tried that, the "contact" state returns undefined as well.

You are running both api calls "at the same time", so we can assume that you will never have the contactId defined at the moment of making the contacts call.
So basically you want to run the contacts call once you have a contactId, not before. For doing so, you can add an extra effect that will be run when contactId value changes.
Also note that in your snippet you are using setActivities instead of setContact after the contacts call.
This will fix said issues
const [activities, setActivities] = useState([""]);
const [contact, setContact] = useState();
const currentActivity = activities[0]
let contactId = currentActivity.contacts
useEffect(() => {
API.getActivities()
.then(res =>
setActivities(res.data)
).catch((err) => console.log(err))
}, []);
useEffect(() => {
// Do nothing when contactId is not defined
if(!contactId) return;
API.getContact(contactId)
.then(res =>
setContact(res.data) // I also modified this line. It was updating the activities in your snippet
).catch((err) => console.log(err))
}, [contactId]); // This effect will be run every time the contactId changes
console.log(currentActivity)
console.log(contactId)
console.log(contact)

let me know if this helps. I have not tested it, may be minor changes required.
useEffect(async() => {
const resActivities=await API.getActivities()
setActivities(resActivities.data)
const promises=resActivities.data.map(item=>API.getContact(item.contactId)) // assuming contactId property exists
const resp=await Promises.all(promises)
setContact(resp.flat())
}, []);

I created a couple of APIs on Wirespec to generate a response that you can test. You can use Wirespec to create your own APIs for free.
getRandomUser API:
https://wirespec.dev/Wirespec/projects/apis/Stackoverflow/apis/getRandomUserId
getRandomUser Response:
https://api.wirespec.dev/wirespec/stackoverflow/getrandomuserid
getUserDetails API:
https://wirespec.dev/Wirespec/projects/apis/Stackoverflow/apis/getUserDetails
getUserDetails Response:
https://api.wirespec.dev/wirespec/stackoverflow/getuserdetails?id=3
Here is the test code. Two solutions are provided:
let userDetails;
new Promise(function (resolve) {
let user = JSON.parse(httpGet("https://api.wirespec.dev/wirespec/stackoverflow/getrandomuserid"));
resolve(user);
}).then(function (user) {
userDetails = JSON.parse(httpGet("https://api.wirespec.dev/wirespec/stackoverflow/getuserdetails?id=" + user.id));
console.log("firstName: " + userDetails.firstName + ", lastName: " + userDetails.lastName);
});
// A shorter way and cleaner approach...
getUserDetails()
.then (function (userDetails) {
console.log("firstName: " + userDetails.firstName + ", lastName: " + userDetails.lastName);
});
async function getUserDetails() {
let user = await JSON.parse(httpGet("https://api.wirespec.dev/wirespec/stackoverflow/getrandomuserid"));
let userDetails = await JSON.parse(httpGet("https://api.wirespec.dev/wirespec/stackoverflow/getuserdetails?id=" + user.id));
return userDetails;
}
function httpGet(url) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", url, false);
xmlHttp.send(null);
return xmlHttp.responseText;
}

Related

How to wait for all the functions and then render in React native?

I am getting data from API's and here is my functions
useEffect(() => {
getSeller();
getReviews();
}, []);
const getSeller = async () => {
let data = await api.get(`/${itemSeller}/`).then(({ data }) => data);
setUser({ user: data[0].first_name + ' ' + data[0].last_name });
};
const getReviews = async () => {
let data = await ReviewsApi.get(`/${itemId}/`).then(({ data }) => data);
data.map((a) => getReviewGiverName(a, data));
};
const getReviewGiverName = async (a, data) => {
let data1 = await api.get(`/${a.buyer}/`).then(({ data }) => data);
a.buyername = data1[0].first_name + ' ' + data1[0].last_name;
setReviews(data);
};
I am adding a new field buyer name to my reviews list which in which I have stored data from API.
But the problem is that if I ```console.log(reviews)```` it displays correctly the buyer name in every object of reviews list but if I render these on-screen through map function.... it displays some buyer name but for some buyer name it shows empty space.
Maybe do it like this will help
const getReviews = () => {
ReviewsApi.get(`/${itemId}/`).then(({ data }) =>
data.map((a) => getReviewGiverName(a, data))
);
};
OR
You can focus on the setReviews state so it will re-render whenever reviews change.
useEffect(() => {
getSeller();
getReviews();
}, [reviews]);
But the problem is that if I console.log(reviews) it displays
correctly the buyer name in every object of reviews list but if I
render these on-screen through map function.... it displays some buyer
name but for some buyer name it shows empty space.
It seems you are enqueueing multiple asynchronous requests in a loop, and each one is independently updating state.
There also appears to be some object mutations going on when getReviewGiverName is appending new buyername property to a which is a review object in data.
You are also mixing async/await syntax with Promise chains. Generally you should pick one or the other.
const getReviews = async () => {
let data = await ReviewsApi.get(`/${itemId}/`)
.then(({ data }) => data);
data.map(a => getReviewGiverName(a, data))
}
const getReviewGiverName = async (a, data) => {
let data1 = await api.get(`/${a.buyer}/`)
.then(({ data} ) => data);
a.buyername = data1[0].first_name + ' ' + data1[0].last_name;
setReviews(data) ;
}
To resolve I suggest mapping the getReviewGiverName requests into an array of Promises and Promise.all them and then update/set the reviews state. In the getReviewGiverName fetch the review's buyer and compose a new review object with buyername property. Promise.all returns an array of the mapped data which is the new reviews state.
const getReviews = async () => {
const { data } = await ReviewsApi.get(`/${itemId}/`);
const reviews = await Promise.all(data.map(getReviewGiverName));
setReviews(reviews)
}
const getReviewGiverName = async (review) => {
const { data } = await api.get(`/${review.buyer}/`);
return {
...review,
buyername: data[0].first_name + ' ' + data1[0].last_name
};
}

React Native - I want to set my session state first before I call my API

I am new to React Native.
If someone can help me then would be great.
How I can set my session state first from AsyncStorage before it goes for API call. Because this API call required sessionId (UserId) so it can return only those data which belong to this userId.
The issue I am currently facing is when API calls for the data it is calling with null seesionId instead of some value which I am getting from AsyncStorage because both methods (settingSession, InitList ) are async.
const [sessionId, setSessionId] = useState(null);
const settingSession = async () => {
await AsyncStorage.getItem('userId').then(val => setSessionId(val));
}
useEffect(() => {
settingSession(); // Setting sessionId
InitList(); // Calling API which required session value
}, []);
const InitList = async () => {
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
try {
// getting sessionId null instead of value from AsyncStorage
const response = await fetch("http://127.0.0.1:8080/skyzerguide/referenceGuideFunctions/tetra/user/" + sessionId, requestOptions)
const status = await response.status;
const responseJson = await response.json();
if (status == 204) {
throw new Error('204 - No Content');
} else {
setMasterDataSource(responseJson);
}
} catch (error) {
console.log(error);
return false;
}
}
I'm thinking of two possible solutions:
Separate InitList() into a separate useEffect call, and put sessionId in the dependency array, so that the API call is only made when the sessionId has actually been updated:
useEffect(() => {
settingSession(); // Setting sessionId
}, []);
useEffect(() => {
InitList(); // Calling API which required session value
}, [sessionId]);
Wrap both functions in an async function within the useEffect call, and call them sequentially using await:
useEffect(() => {
const setSessionAndInitList = async() => {
await InitList(); // Calling API which required session value
await settingSession(); // Setting sessionId
}
setSessionAndInitList()
}, []);
Let me know if either works!

Multiple nested axios calls don't resolve as expected

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 '';
}
};

How tu turn this function into a thunk react promise function (Updated)

I am fetching data from the blockchain. Contract adresses in this case.
Once I have the adresses I fetch some info on each specific adresses and add a key=>value pair to the object. This is all working and I'm getting all the right data. However, once in the component, the newly added key=>value pair is no longer present. I think this is because the value added is a promised and the dispatch is not waiting on it. How can I fix this so the dispatch it done only once the promised is resolved.
const tokenStream = await exchange.getPastEvents('OtherToken', {fromBlock:0, toBlock: 'latest'})
const allTokens = tokenStream.map((event) => event.returnValues)
console.log('ALL TOKEN DATA : ', allTokens)
allTokens.forEach( async element => {
let symbol = await exchange.methods.getERCsymbol(element.tokenAddress).call()
element.symbol = symbol
});
console.log('ALL TOKEN DATA AFTER : ',allTokens) // I see symbol
dispatch(allTokensLoaded(allTokens))
Better solution would be to use Promise.all to wait for multiple async request/promise to finish, and also you are mixing await and then, as your main function is already async you can write it in more neat and clean way using await only.
export const loadAllTokens = async (exchange, dispatch) => {
const result = await exchange.getPastEvents('OtherToken', {fromBlock:0, toBlock: 'latest'});
const allTokens = result.map((event) => event.returnValues);
await Promise.all(allTokens.map(async (element) => {
const innerResult = await exchange.methods.getERCsymbol(element.tokenAddress).call();
element.symbol = innerResult;
element[2]= innerResult;
}));
dispatch(allTokensLoaded(allTokens));
}
its more clean and better to understand :).
if any doubts please comment.
They happens to be nested async request and this is how I fixed it
export const loadAllTokens = async (exchange, dispatch) => {
await exchange.getPastEvents('OtherToken', {fromBlock:0, toBlock: 'latest'}).then( async(result) => {
const allTokens = result.map((event) => event.returnValues)
let count = 0
allTokens.forEach( async element => {
await exchange.methods.getERCsymbol(element.tokenAddress).call().then( async(result) =>{
element.symbol = result
element[2]= result
count += 1
}).then( () => {
if(count === allTokens.length){
dispatch(allTokensLoaded(allTokens))
}
})
})
})
}

React hooks dependencies, including it creates an infinite loop, not including it doesn't give me the latest value

Using React hooks.
I'm trying to do a simple API fetch call with some data, but I can't seem to make this work.
Here is the sandbox link
In this example, the objective is that every 5secs, it fetches to the server to get any updates to the username since the latest latestUpdate.
But for convenience, I will include the code here as well:
const SmallComponent = () => {
const { id, username, latestUpdate } = useItemState();
const dispatch = useItemDispatch();
console.log("Render id", id, "Latest", latestUpdate);
const fetchUsername = useCallback(async () => {
console.log("Getting Id", id, "Latest", latestUpdate);
const response = await fetch(
"https://jsonplaceholder.typicode.com/users/" + id
);
const user = await response.json();
dispatch({ type: "setUsername", usernameUpdated: user.name });
}, [dispatch, id]);
const updateId = useCallback(() => {
dispatch({ type: "setId", id: id + 1 });
}, [dispatch, id]);
useEffect(() => {
fetchUsername();
const refresh = setInterval(() => {
updateId();
}, 5000);
return () => clearInterval(refresh);
}, [fetchUsername, updateId]);
return (
<div>
<h4>Username from fetch:</h4>
<p>{username || "not set"}</p>
</div>
);
};
As you'll notice, my fetchUsername is missing a dependency for latestUpdate (which is used on my server to only send udpates since that date). I update latestUpdate when the fetchUsername is finished in my reducer.
What I need:
on mount: fetch username => updates state for username and latestUpdate
interval: every 5secs => fetch updates to username and update latestUpdate to new Date()
The problem is:
If I add the dependency to the useCallback for fetchUsername, I get an infinite refresh loop.
If I don't add it, my latestUpdate value is wrong (ie initial value)
What am I doing wrong?
As you're not using the fetch method anywhere else, it makes sense to put it inside the useEffect directly. No need for useCallback:
useEffect(() => {
const fetchUsername = async () => {
console.log("FETCH", latestUpdate);
const url =
"https://jsonplaceholder.typicode.com/users/" + id + "#" + latestUpdate;
const response = await fetch(url);
const user = await response.json();
dispatch({ type: "setUsername", usernameUpdated: user.name });
};
const refresh = setInterval(() => {
fetchUsername();
}, 5000);
return () => clearInterval(refresh);
}, [dispatch, id, latestUpdate]);
Here is the full CodeSandBox:
https://codesandbox.io/s/trusting-framework-hvw06?file=/src/App.js
You can find more in the official docs (look for "...to move that function inside of your effect"):
https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
And I also recommend Robin Wieruch's hook-fetching tutorial: https://www.robinwieruch.de/react-hooks-fetch-data
In general, I would highly recommend using something like react-query, as it will also take care of caching. It is a better way to consume your server data (instead of fetching and putting the response in your context): https://github.com/tannerlinsley/react-query

Resources