Set State from ajax call to insert into db outside function? - reactjs

async componentDidMount(){
// Metrics
await this.forumMetrics();
}
I'm trying to insert data from an API that I'm calling from my ajax call. However, when I try to set state inside the function, it tells me that setstate is not a function. I looked this up and a lot of posts told me I had to .bind(this) which I did but it's still giving me the error.
Futhermore, I also ran into another problem of not being able to access the state from outside the function cause I need to insert it into my service worker that inserts the data into my db. I have access within the function but not outside which I need in order to bind the state to my model in my back-end.
Any suggestions? I feel like I'm close but missing something.
Here is my code:
async forumMetrics(data){
const getuserData = () => {
$.getJSON({
type: 'GET',
url: 'https://json.geoiplookup.io/api?callback=?',
success: (data) => {
//console.log(data);
this.setState({
userIp: data.ip,
userCity: data.city,
userRegion: data.region,
userCountry: data.country_name,
userLatitude: data.latitude,
userLongitude: data.longitude
})
//console.log(this.state);
},
})
}
const result = getuserData();
result;
const metricData = {
userIp: this.state.userIp,
userCity: this.state.userCity,
userCountry: this.state.userCountry,
userRegion: this.state.userRegion,
userLatitude: this.state.userLatitude,
userLongitude: this.state.userLongitude,
vieweddateTime: Moment().format('YYYY-MM-DD hh:mm:ss'),
createdDate: Moment().format('YYYY-MM-DD hh:mm:ss'),
year: Moment().format('YYYY'),
month: Moment().format('MM')
}
const metricForum = await MetricService.insertpageView(metricData);
}

You should really not use jQuery to retrieve data. Use fetch() or axios or something like it.
That being said, your problem can be solved by using arrow functions. Instead of function getuserData..., try this:
const getuserData = (result) => {
$.getJSON({
type: 'GET',
url: 'https://json.geoiplookup.io/api?callback=?',
success: (data) => {
console.log(data);
// I have access to data in this function
this.setState({
userIp: data.ip
})
},
})
}
Note that the this in the arrow functions is bound to the parent function's this.
Also, remember to bind your forumMetrics function on the component constructor.
EDIT:
I don't see you calling getuserData() anywhere...
EDIT 2:
You might want to make the getUserData a separate functionlike so:
async forumMetrics(data){
const data = await this.getuserData();
console.log(data);
this.setState({
userIp: data.ip,
userCity: data.city,
userRegion: data.region,
userCountry: data.country_name,
userLatitude: data.latitude,
userLongitude: data.longitude
});
}
getUserData(data) {
return new Promise((accept, reject) => {
$.getJSON({
type: 'GET',
url: 'https://json.geoiplookup.io/api?callback=?',
success: accept,
})
});
}
note that the await keyword inside an async function will wait for a promise to finish and use the accepted value to keep going.
You could simplify the getUserData function even more by just using fetch
getUserData() {
return fetch('https://json.geoiplookup.io/api?callback=?');
}

forumMetrics() call
Your forumMetrics function isn't bound, so this is most likely undefined. You can either bind it in the constructor
constructor() {
this.forumMetrics = this.forumMetrics.bind(this);
}
or declare it like:
forumMetrics = async () => {
}
The latter is experimental syntax though.
setState() call
If you go for the first option, inside the callback, your this is definitely not pointing to the class. If you're going to use jQuery for the ajax fetch, then capture the reference in a self variable (which is a bad practice, but so is using jQuery on React):
async forumMetrics(){
const self = this;
function getuserData(result) {
...
self.setState({ ...stuff here... })

Related

Axios request returning promise Object

Not sure why when I log out the price after executing the fetchPrice() method, I am returned a promise object instead of price. Please help :)
async function fetchPrice() {
await axios
.get(assetData(selectedAsset))
.then((response) => {
return response.data.market_data.current_price.eur;
})
.catch((error) => console.log(error.response.data.error));
}
useEffect(() => {
let price = fetchPrice();
console.log("Asset Price: " + price);
let assetTotal = Number(cost) / Number(price);
setAssetAmount(assetTotal);
}, [selectedAsset]);
The problem is how you are handling the await of your function. Normally when working with promises you use either await or a callback, not both. Because await already wait for the promise to resolve or throw an error, you directly use the return inside the function and not in the callback.
async function fetchPrice() {
try {
const price = await axios.get(assetData(selectedAsset));
return price;
} catch (e) {
console.log(error.response.data.error)
}
}
Using return inside the callback doesn't return the object you expect since it is the result of your callback function and not the original function.
Since fetchPrice is an async function it is normal that if you try to print it you will obtain a promise. This won't change even if you correct your function as I told you above. The only way to obtain the object is by awaiting your fetch price inside the useEffect (and by making the useEffect itself async) like this:
useEffect(async () => {
let price = await fetchPrice();
console.log("Asset Price: " + price);
let assetTotal = Number(cost) / Number(price);
setAssetAmount(assetTotal);
}, [selectedAsset]);
However, if you do it, you will obtain the following warning because your use effect use should be synchronous:
Effect callbacks are synchronous to prevent race conditions. Put the async function inside
The final solution? Set state directly inside the callback.
async function fetchPrice(cost) {
try {
const price = await axios.get(assetData(selectedAsset));
setAssetAmount(Number(cost)/Number(price))
} catch (e) {
console.log(error.response.data.error)
}
}
However, be careful of memory leaks when setting a state asynchronously inside a component that can be unmounted.
You need to use either .then() or async/await.
Example of then()
useEffect(() => {
axios.get("https://api.github.com/users/mapbox").then((response) => {
console.log(response.data);
});
}, []);
In above code, we are using .then() so then will be fired when api call is done.
Example of async/await
async function axiosTest() {
const response = await axios.get("https://api.github.com/users/mapbox");
console.log(response.data, "with await");
}
In above code, we are using await to wait until the api call is done.
Here is the Codesandbox to check the difference with live api call

Why does my axios call return undefined even after then is resolved?

I'm currently using functions to predefine all of my axios calls so for example:
export const getClients = () => {
axios.get("/client/")
.then(response=>{
return response;
})
.catch(error=>{
return error;
});
};
Now, I want to call this in a class-based component in the componentDidMount like this:
componentDidMount(){
this.setState({
clients: getClients()
});
}
I can't seem to figure out why when I try to console.log(this.state.clients) at the end of componentDidMount I'm getting an undefined error. I'm new to React and from what I understand, the then in the function of the axios call should resolve the promise and return the actual response from the API call so when I call getClients(), the clients state should be the response.
What am I doing wrong?
componentDidMount(){
fetchClients();
}
const fetchClients = () => {
getClients().then( (response)=> {
// handle success
this.setState({clients:response});
});
};
Okay there is some stuff that needs to be cleared out here :-
You need to change getClients like so :-
export const getClients = () => {
return axios.get("/client/")
.then(response=>{
return response;
})
.catch(error=>{
return error;
});
};
Why ?
Because the value that you returned from the callback of then is wrapped inside a Promise implicitly and has to be returned for consumption as you do in a function like function sum(a,b) {return a+b}
componentDidMount will change like so :-
componentDidMount(){
const fetchClients = async () => {
const clients = await getClients();
this.setState({clients});
}
fetchClients();
}
Why ?
If you want to use getClients in a statement fashion instead of .then().then() promise chain, you will first wrap it inside an async function and then call await on getClients() (remember this function returns a Promise) and then set the state inside that async function.
Even if you console.log the clients state after fetchClients() within componentDidMount, you probably won't see the value set because setState works asynchronously. So never rely on the console.log of your state just after setting it.

How can I optimize my code to stop sending GET requests constantly?

I am using the Yelp Fusion API to get a list of restaurants from Yelp. However, I am always constantly sending a GET request and I am not sure what is going on or how to fix it. I have tried React.memo and useCallback. I think the problem lies within how I am making the call rather than my component rerendering.
Here is where I send a GET request
// Function for accessing Yelp Fusion API
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
// Saving our results, getting first 5 restaurants,
// and turning off our loading screen
setYelpResults({businesses: response.data.businesses.splice(0, 5)});
setEnableLoading(1);
}
catch (error) {
setEnableLoading(2);
}
};
This is where I use axios.
// Our Yelp Fusion code that sends a GET request
export default axios.create({
baseURL: `${'https://cors-anywhere.herokuapp.com/'}https://api.yelp.com/v3`,
headers: {
Authorization: `Bearer ${KEY}`
},
})
You are probably calling that function within your functional component and that function sets a state of that component, so it re-renders. Then the function is executed again, sets state, re-renders and so on...
What you need to do is to wrap that API call inside a:
useEffect(() => {}, [])
Since you probably want to call it one time. See useEffect doc here
You can do 2 things either use a button to get the list of restaurants because you are firing your function again and again.
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
Use a button instead maybe so once that button is clicked function is fired.
<button onClick={yelpFusionSearch} />Load More Restaurants </button>
Use your fuction inside useEffect method which will load 5 restaurants once the page renders
useEffect(() => {
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
}, [])

How to access data after asynchronous api call using react/ redux saga ? More info below

Tech using: React, Redux, Saga
how do I access data right after from second function call? The firstFunction keeps executing the rest of the code. I tried async/await, but it does not work.
firstFunction (()=>{
secondFunctionAPI() //using Redux and Saga
// Here I want to use the data from the response of secondFunction().
more code...
more code..
})
You could pass the code you want to execute as a callback to secondFunctionAPI
firstFunction(() => {
secondFunctionAPI((data) => {
// Code using data object
})
})
secondFunctionAPI would look something like that:
secondFunctionAPI = (callback) => {
API.fetchSomething().then(response => {
// Call back with data object
callback(response.data);
})
}
You could invoke second function by dispatching an action after api call like below:
const response = yield call(firstFunction);
yield put({ type: 'SOME_ACTION', payload: response.data });
Then have a watcher saga defined which will wait for action of type 'SOME_ACTION' and call the handler once this action is dispatched.
yield takeLatest('SOME_ACTION', second function)
And then define your handler for 'SOME_ACTION' like:
function* secondFunction(action) {
// here action is the object which was sent as param in put() method earlier
const { type, payload } = action;
// your logic here
}
Approach 1: Callbacks
Like others have noted, you can make secondFunctionAPI take in a callback parameter, then call it:
firstFunction (() => {
secondFunctionAPI(() => {
// More code here...
});
});
Approach 2: Promises
Convert both functions to use promises, like so:
function secondFunctionAPI(){
return new Promise((resolve, reject) => {
// do stuff, then:
resolve();
});
}
Then, call your functions like this:
firstFunction().then(() => {
secondFunctionAPI().then(() => {
// More code here...
});
});
Promise.all
If you can call both functions simultaneously (secondFunctionAPI does not need the result of firstFunction to run), you can use this for efficiency:
Promise.all([
firstFunction(), secondFunctionAPI()
]).then(() => {
// ...
});
Approach 3: async/await
Make your functions return promises, like above.
Make a wrapper async function.
async function doStuff(){
try { // This is how you would catch rejections
await firstFunction();
} catch(error) {
console.error(error);
}
var foo = await secondFunctionAPI(); // This is how you get the result
}

React Native Why is my code executing before finishing the task? Promise.all().then() asynchronous issues

I have this set up where it runs through an array, and saves it into the phone, but it opens the googleUrl before all the media files are downloaded. Shouldn't the Promise.all() take care of this? Isn't it supposed to wait for mapMediaArray to be finished, and .then() the rest of the work?
const mapMediaArray = selectedMedia.map(index => {
let cleanUrl = `${index.mediaUrl.split('?')[0]}`;
let extension = cleanUrl.split('.').pop();
RNFetchBlob.config({
fileCache: true,
appendExt: extension,
})
.fetch('GET', index.mediaUrl)
.then(res => {
CameraRoll.saveToCameraRoll(res.path());
});
});
Promise.all(mapMediaArray).then(() => {
Linking.openURL(googleUrl);
});
It is true that you call a promise inside your method but your method is not a promise. It a synchronous method so as soon as all synchronous codes gets called promise.all() is called. Your method must be something like this to be identified as a valid promise.
const mapMediaArray = selectedMedia.map(index => {
return new Promise((resolve)=>{
let cleanUrl = `${index.mediaUrl.split('?')[0]}`;
let extension = cleanUrl.split('.').pop();
RNFetchBlob.config({
fileCache: true,
appendExt: extension
})
.fetch('GET', index.mediaUrl)
.then(res => {
CameraRoll.saveToCameraRoll(res.path());
resolve()
})
})
}
Like above code, you have to return a promise and then call resolve in order to make Promise.all work correctly.

Resources