Not receiving id from api call in a async loop - reactjs

I'm trying to send an array of tweets and use the id returned from one tweet in the body of the next tweet in the array but it doesn't seem to be working.
The id is constantly 0 meaning the call doesn't update the id when each call is finished.
const postTweet = async () => {
let x = 0;
tweetData.forEach(async (data, i) => {
x = await fetch(`${process.env.REACT_APP_SERVER_URL}/new-tweet`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
cache: 'no-cache',
body: JSON.stringify({
status: data.tweetText,
in_reply_to_status_id: x
})
})
.then(res => res.ok ? res.json() : updateErrorStatus(true))
.then(({ replyToId }: { replyToId: number }) => replyToId)
})
}
Can anyone see where I'm going wrong?

The reason why x is not getting updated is that forEach does not wait for the promises to resolve (even if you use the await keyword), so all the requests are firing at the same time. Here's a little example to illustrate the point
const myAsyncFunc = ()=> new Promise((resolve) => {setTimeout(()=>{resolve('resolved')}, 1000)})
const arr=[1,2,3]
async function try1(){
arr.forEach(async i => {let r= await myAsyncFunc(); console.log("1 "+r);})
}
async function try2(){
for(const a of arr)
{
let r = await myAsyncFunc();
console.log("2 "+r)
}}
try1(); try2();

Related

React Fetch with State Value

I've two different API URL. I can get current user's ID with /api/current_user and save into "currentuser" state. I want to fetch all currentuser's from MySQL. My API URL works. But i couldn't fetch with currentuser state variable.
This link returns currentuser's ID. It works.
useEffect(()=>{
fetch('http://localhost:8000/api/current_user/', {
headers: {
Authorization: `JWT ${localStorage.getItem('token')}`
}
})
.then(res => res.json())
.then(json => {
setCurrentuser(json.id);
});
},[])
Then i want to use that ID with currentuser state.
Axios.request({
method: 'POST',
url: 'http://localhost:3001/api/post',
data: {
curus: `${currentuser}` // I'm trying to use currentuser state on here.
},
})
.then(response => {
return response.data;
})
.then(data => {
let tmpArray2 = []
const tmpArray = []
bla bla bla ...
Finally request payload returns curus: ""
So it have a null value. I can use this state value inside return function.
Also that's my node server's index.js:
app.post('/api/post', (req, res) => {
const currentt = req.body.curus
const sqlSelect = "SELECT * FROM messagestable WHERE sender='" + currentt + "' OR recipient ='" + currentt + "' ";
db.query(sqlSelect, (err, result) => {
res.send(result);
console.log(currentt)
});
})
I want to fetch all messages from MySQL but just for currentuser. Not all users messages. May you help me? Thanks a lot!
You can't call fetch and Axios.request in succession because setCurrentuser is async and when you use currentuser in Axios.request you don't know if currentuser has the very last value.
Much better split fetch and Axios.request into 2 useEffect in this way:
useEffect(()=>{ //<-- this will be fired on component's loading
fetch('http://localhost:8000/api/current_user/', {
headers: {
Authorization: `JWT ${localStorage.getItem('token')}`
}
})
.then(res => res.json())
.then(json => {
setCurrentuser(json.id);
});
},[])
useEffect(() => { //<-- this one will be fired every time you change currentuser and will contains the very last value of currentuser
Axios.request({
method: 'POST',
url: 'http://localhost:3001/api/post',
data: {
curus: `${currentuser}`
},
})
.then(response => {
return response.data;
})
.then(data => {
let tmpArray2 = []
const tmpArray = []
bla bla bla ...
}, [currentuser])

ReactJS: wait until state is filled before making next call

I have quite a big function that retrieves a bunch of information about Spotify playlists. Because the data is paginated I have a to make a couple of calls and append data to the state recursively. After that's done, I want to pass the state along with a POST request to another endpoint, to make some calculations. The returned values are then stored in state as well.
const fetchPlaylist = (playlistId) => {
showLoading()
setTitles([])
setArtists([])
setFeatures([])
setTSNEfeatures([])
setIds([])
setLabels([])
const getPlaylistDataRecursively = (url) => {
return fetch('/spotify/get-track-ids', {headers: {
'url': url
}})
.then(response => response.json())
.then(data => {
console.log(data)
setTitles(titles => ([...titles, ...data.title]))
setArtists(artists => ([...artists, ...data.artist]))
setFeatures(features => ([...features, ...data.features]))
setIds(ids => ([...ids, ...data.track_ids]))
if (data.next_url) {
const next_url = data.next_url.replace('https://api.spotify.com/v1', '')
return getPlaylistDataRecursively(next_url)
} else {
return fetch('/spotify/get-dimension-reduction', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(features)
})
.then(response => response.json())
.then(data => {
setTSNEfeatures(data)
})
}
})
}
return getPlaylistDataRecursively(`/playlists/${playlistId}/tracks/?offset=0&limit=100`)
.then(() => {
hideLoading()
});
}
The problem is that fetch('/spotify/get-dimension-reduction' ... ) is ran before getPlaylistDataRecursively is done filling the features state. How can I tackle this issue?

How to optimize APIs function with the same header

I'm using fetch to deal with the API, I must add a header into every single API function like below. It's duplicate so much.
getSomething: async() => {
const user: IUserData = await JSON.parse(localStorage.getItem("token")!),
userCompanies: ICompanies = await JSON.parse(localStorage.getItem("companies")!),
companyId = Object.keys(userCompanies.Company)[0], meId = user.userInfo.data.id, myHeaders = new Headers();
myHeaders.append("authorization", user.accessToken.token);
myHeaders.append("company-id", companyId);
const requestOptions: RequestInit = {
headers: myHeaders,
method: "GET",
redirect: "follow"
};
return fetch(url + `/${meId}/calendars?type=tree`, requestOptions)
.then((response) => response.json())
.then((result) => result)
.catch((error) => error);
}
How can I reduce or optimize them?
Try abstracting fetch by making your own GET method, e.g.
async function get(url: string, options?: RequestInit) {
const user: IUserData = await JSON.parse(localStorage.getItem("token")!);
const userCompanies: ICompanies = await JSON.parse(localStorage.getItem("companies")!);
const companyId = Object.keys(userCompanies.Company)[0];
const meId = user.userInfo.data.id;
const myHeaders = new Headers();
myHeaders.append("authorization", user.accessToken.token);
myHeaders.append("company-id", companyId);
const baseOptions: RequestInit = {
headers: myHeaders,
method: "GET",
redirect: "follow"
};
// merge passed options with your baseOptions
let baseUrl = 'your-base-url';
return fetch(baseUrl + `/${meId}/${url}`, baseOptions)
.then((response) => response.json())
.then((result) => result)
.catch((error) => error);
}
getSomething: async() => {
return get('/calendars?type=tree')
}
This is just an idea, because I'm not sure which parameters are reusable over many different requests. You might even end up with baseGet which handles only core functionality shared for all requests, then have baseCompanyGet which is abstraction of baseGet and is used only by company requests, etc..
The idea is to identify code that is being repeated and put it in some kind of base method, while you pass only dynamic data via method parameters.
I guess the way you could do that is by grouping it in a storage class that you find helpful when accessing the data
//Setter
localStorage.setItem('myHeaders', data);
// getter
localStorage.getItem('myHeaders');
To work with local storage have a look at this article
https://medium.com/#siobhanpmahoney/local-storage-in-a-react-single-page-application-34ba30fc977d

Call two fetch function with setState in between

I am trying to call two fetch APIs to store the information.
After first API call to cloudinary I get the URL back, which I want to store in state before hitting second API to store info in database. I am getting setAddOnData undefined error. While I try to make second API call , code functions properly for single API
const onSubmit = e => {
e.preventDefault();
console.log(fileInputRef.current.files[0]);
const myNewCroppedFile = fileInputRef.current.files[0];
console.log(myNewCroppedFile);
const formData = new FormData();
formData.append('file', myNewCroppedFile);
formData.append('upload_preset', 'xprl6rwq');
const options = {
method: 'POST',
body: formData
};
const addOnBody = JSON.stringify({ itemName, itemIconURL, itemPrice });
const config = {
headers: {
'Content-Type': 'application/JSON'
}
};
const option2 = {
method: 'POST',
body: addOnBody
};
return fetch(
'https://api.Cloudinary.com/v1_1/antilibrary/image/upload',
options
)
.then(res => res.json())
.then(res =>
setAddOnData({
...addOnData,
itemIconURL: res.secure_url
})
)
.then(fetch(`/api/v1/addOn`, option2, config))
.then(res => res.json());
};
const [addOnData, setAddOnData] = useState({
addOnCategory: '',
itemName: '',
itemPrice: '',
itemIconURL: '',
itemIconFileName: '',
step: 1
});
setState calls are asynchronous. When you run your second fetch call, the setAddOnData hasn't necessarily updated the addonData variable yet. You'd be best off moving the second fetch call to a useEffect which is dependent on the data returned from your first fetch call.
const addOnBody = JSON.stringify({ itemName, itemIconURL, itemPrice });
const config = {
headers: {
'Content-Type': 'application/JSON'
}
};
const option2 = {
method: 'POST',
body: addOnBody
};
const onSubmit = e => {
...
return fetch(
'https://api.Cloudinary.com/v1_1/antilibrary/image/upload',
options
)
.then(res => res.json())
.then(res =>
setAddOnData({
...addOnData,
itemIconURL: res.secure_url
})
)
};
useEffect( () => {
fetch(`/api/v1/addOn`, option2, config)
.then(res => res.json());
},[addOnData.itemIconURL])

Unable to get response using fetch in React

I am trying to call 3rd party API, to fetch some data. I am getting the response in Postman, but not getting expected response when I execute my code.
I tried in 2 ways. Both ways I am getting "Promise pending".What could be the reason??
//request.js
Method 1
export const callSearchGiftsAPI = inputs => dispatch => {
dispatch(searchGifts());
let url = new URL(GIFT_SEARCH_API_URL),
params = {
apiKey: GIFT_SEARCH_API_KEY,
query: inputs.item,
country: 'us',
itemsPerPage: 3
};
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));
return new Promise((resolve, reject) => {
setTimeout(() => resolve(
fetch(url, {
method: 'GET',
// mode: 'no-cors',
headers: {
'Content-Type': 'application/json',
Authorization: `secret ${SECRET}`
}
})
.then(res => {
if (!res.ok) {
return Promise.reject(res.statusText);
}
console.log("hi", res.json());
return res.json();
})
.then(gifts => dispatch(searchGiftsSuccess(gifts)))
.catch(err => dispatch(searchGiftsError(err)))), 500)
});
}
Method 2:
export const callSearchGiftsAPI = inputs => dispatch => {
dispatch(searchGifts());
let url = new URL(GIFT_SEARCH_API_URL),
params = {
apiKey: GIFT_SEARCH_API_KEY,
query: inputs.item,
country: 'us',
itemsPerPage: 3
};
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));
fetch(url, {
method: 'GET',
// mode: 'no-cors',
headers: {
'Content-Type': 'application/json',
Authorization: `secret ${SECRET}`
}
})
.then(res => {
if (!res.ok) {
return Promise.reject(res.statusText);
}
console.log('result', res.json());
return res.json();
})
.then(gifts => dispatch(searchGiftsSuccess(gifts)))
.catch(err => dispatch(searchGiftsError(err)));
};
//form.js
class Form extend React.Component{
onSubmit(values) {
const inputs = Object.assign({}, values);
return this.props.dispatch(callSearchGiftsAPI(inputs));
}
//Remaining code
}
Also please note that I have installed CORS plugin in Chrome, to allow the request.If I disable it and add mode:'no-cors' I am getting as 401 unauthorized.What else am I supposed to do?
What happens is that you are creating a new Promise and returning it, but you are not waiting for it to resolve. You can either use then of the new async/await syntax to get the correct result :
onSubmit = async values => {
const inputs = Object.assign({}, values);
return await this.props.dispatch(callSearchGiftsAPI(inputs));
}
The code above will work with your first method.
Since your second method does not return anything, you will never get your result, you need to return your fetch's result and apply the code I gave above :
return fetch(url, {
This worked.
I was trying to put console.log in the wrong place and hence was not able to see the response properly.
export const callSearchGiftsAPI = inputs => dispatch => {
dispatch(searchGifts());
let url = new URL(GIFT_SEARCH_API_URL),
params = {
apiKey: GIFT_SEARCH_API_KEY,
query: inputs.item,
country: 'us',
itemsPerPage: 3
};
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));
console.log(url);
return fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `secret ${SECRET}`
}
})
.then(res => {
console.log('result');
return res.json();
})
.then(response => {
console.log(response); // changed
dispatch(searchGiftsSuccess(response.items));
})
.catch(err => dispatch(searchGiftsError(err)));

Resources