How to update data after fetch? - javascript

I'm fetching data on componentDidMount in fetchData method. After that I'm trying to delete data with the method. I was trying to update date immediately in deleteUser method, but it doesn't work. How can I update this data after fetch with delete method?
fetchData = () => {
let url = `https://contact-browser.herokuapp.com/contacts`;
fetch(url, {method: 'get'}).then(resp => resp.json()).then((data) =>
this.setState({
data: Object.entries(data).map(([key,value])=>({key:Number(key),value}))
})
);
};
componentDidMount() {
this.fetchData();
}
deleteUser = (id) => {
let url = `https://contact-browser.herokuapp.com/contact/${id}`;
fetch(url, {method: 'delete'}).then(resp => console.log(resp));
this.fetchData();
};

Refetch your data after the delete fetch has fulfilled, this ensures that the data is fetched after the delete has fully resolved on the server.
deleteUser = (id) => {
let url = `https://contact-browser.herokuapp.com/contact/${id}`;
fetch(url, {method: 'delete'}).then(resp => {
this.fetchData();
});
};

It "did not work" because of the async behaviour of JS.
To be simple, when there is any waits in the code (like DB Call, API Call, etc), the JS code lets that particular to run and starts executing the next line. More on this - https://www.hongkiat.com/blog/synchronous-asynchronous-javascript/
In this case, since fetch is an i/o wait, the control moves to the next line - this.fetchData(). So, the fetchData() is called before delete may actually happens.
Following is another solution using async and await. More to read
deleteUser = async (id) => {
let url = `https://contact-browser.herokuapp.com/contact/${id}`;
let resp = await fetch(url, {method: 'delete'});
console.log(resp);
this.fetchData();
};

You can also use async and await
deleteUser = async (id) => {
let url = `https://contact-browser.herokuapp.com/contact/${id}`;
const deleted = await fetch(url, {method: 'delete'}).then(resp => console.log(resp));
if(deleted){
await this.fetchData();
}
};

Related

Cannot render and map POST request array promise

I have an API called getQuote and a component called QuoteCard. Inside QuoteCard I'm trying to render an array of users that liked a quote. The API works fine, I have tested it, and the code below for getting the users works fine too.
const Post = async (url, body) => {
let res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"accept": "*/*"
},
body: JSON.stringify(body)
}).then(r => r.json());
return res;
}
const getAllLikes = async () => {
let users = await Post('api/getQuote', {
id: "639e3aff914d4c4f65418a1b"
})
return users
}
console.log(getAllLikes())
The result is working as expected :
However, when trying to map this promise result array to render it onto the page is where I have problems. I try to render like this:
<div>
{getAllLikes().map((user) => (
<p>{user}</p>
))}
</div>
However, I get an error that states:
getAllLikes(...).map is not a function
I don't understand why this is happening. Why can't I map the array? Is it because it's a promise or something?
And if anyone needs to see the getQuote API, here it is:
//Look ma I wrote an API by myself! :D
import clientPromise from "../../lib/mongodb";
const ObjectId = require('mongodb').ObjectId;
import nc from "next-connect";
const app = nc()
app.post(async function getQuote(req, res) {
const client = await clientPromise;
const db = client.db("the-quotes-place");
try {
let quote = await db.collection('quotes').findOne({
_id: new ObjectId(req.body.id)
})
res.status(200).json(JSON.parse(JSON.stringify(quote.likes.by)));
} catch (e) {
res.status(500).json({
message: "Error getting quote",
success: false
})
console.error(e);
}
})
export default app
Thanks for any help!
It is due to the fact that getAllLikes is an async function and thus it returns promise which does not have a map function.
You can either save it in a state variable before using await Or chain it with .then.
Minimal reproducible example which works
const getAllLikes = async () => {
return ['a', 'b']
}
getAllLikes().then((r) => r.map((g) => { console.log(g) }))
Edit: The above code won't work if directly used with jsx since the return of getAllLikes will still be a promise. Solution would be to save it in a state variable and then using it.
I am from Angular and I believe we call pipe on Observables (or Promises). Map can then be called inside the pipe function
observable$ = getAllLikes().pipe(map( user => <p>{user}</p>))
If there is no pipe, I can only think of manually subscribing (which is not a good practice)
sub$ = getAllLikes().subscribe( user => <p>{user}</p>)
// unsub from sub$ appropriately
// We do this from ngOnDestroy in angular
ngOnDestroy() {
this.sub$?.unsubscribe()
}

How to send a GET request with SetTimeout and get data if it is not ready yet?

On Submit click I send a POST request with some data to the server and in response I get an object with id and timeout. Then I need set timeout and when time comes send a GET request for the data. The problem is that data is not ready yet and I get undefined and my React app crashes.
I was told that timeout should be from the first request only (I mean I can't mannualy increase it or do something like this: timeout * 2, I need to use timeout from the first request only). How can I do that? I think it can be done somehow with While loop ...but I'm not smart enough to write this code. Please help
const [someData, setSomeData] = useState({}) // here comes undefined and app crashes because this object renders in DOM
const getData = async (id) => {
const response = await fetch(`$BASE_URL/${id}`)
setSomeData(response)
}
const onSubmit = async (data) => {
const { id, timeout } = await fetch(url, data)
setTimeOut(() => {
getData(id) // data is not ready and I get undefined
}, timeout) // its around 1000ms and I can't change it mannually
}
If I do this then everything works fine
const getData = async (id) => {
const response = await fetch(`$BASE_URL/${id}`)
setSomeData(response)
}
const onSubmit = async (data) => {
const { id, timeout } = await fetch(url, data)
setTimeOut(() => {
getData(id)
}, 6000) // if I manually specify timeout to 6000
}
fetch will return a promises, you could use then to getData.
const onSubmit = async (data) => {
fetch(url, data)
.then(res => return res.json())
.then(res => getData(res.id))
}

React fetch data fires over and over again

Does anyone know why this fetch continues to fire. I have also tried putting it inside a useEffect with no luck. It should only fire once to return once imdbID has loaded.
const WatchOnList = ({ imdbId }) => {
const [locations, setLocations] = useState([])
var headers = new Headers();
headers.append("x-api-key", "API_KEY")
var requestOptions = {
method: 'GET',
headers: headers,
crossDomain: true,
redirect: 'follow'
};
async function fetchData() {
const res = await fetch(`${awsApiUrl}?imdb_id=${imdbId}`, requestOptions);
res
.json()
.then((res) => {
setLocations(res)
console.log(locations)
})
.catch(error => console.log('error', error));
}
fetchData();
With the current structure, the request will fire on every re-render. Which will be quite often in a React app. useEffect is the right place for such a function. But there are some caveats:
You can't make useEffect async, you have to create an async function inside the hook instead and call it afterward.
useEffect will per default run on every update, so you have to tell it explicitly to only run once (like componentDidMount for class components). This can be done by passing an empty array as the second parameter. The hook watches parameters specified in this array and only updates when one of them changes. As it is empty, it only fires once on initialization.
This should work:
useEffect(() => {
async function fetchData() {
const res = await fetch(`${awsApiUrl}?imdb_id=${imdbId}`, requestOptions);
res
.json()
.then(res => {
setLocations(res);
console.log(locations);
})
.catch(error => console.log("error", error));
}
fetchData();
}, []);
Read more about the behavior of hooks here and here.

Centralized Axios API Cancel

I have a React application and I have a centralized API Class. So All of the API calls I make, go through APIClass. I call a specific function of an API class with props and that fetches the data using APICaller and returns it asynchronously. The problem is that I need to implement axios cancel token for it and not sure how could I achieve it. In the below setup, callAPIone would return the data while callAPItwo would fail. How can I make it so second call would cancel first one?
let i = await this.apiClass.callAPIone(1);
let y = await this.apiClass.callAPIone(2,cancel);
Then within apiClass, I have functions like
callAPIone = async (param,cancel) => {
return await ApiCaller(param,cancel)
}
Then within ApiCaller I have
async function ApiCaller(param,apiCancel) {
let source = axios.CancelToken.source();
if (apiCancel) source.cancel("Request canceled.");
let params = param
return await axios
.get(apiRoute, { headers, params, cancelToken: source.token })
.then((response) => OnSuccess(response))
.catch((e) => OnError(e.message));
}

How to make a Javascript/React/Typescript fetch call asynchronous?

Consider the following Javascript/React code:
// Javascript function that has a fetch call in it.
export const signIn = (email:string, password:string) => {
console.log("FETCHING...");
fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
.then((response) => {
return response.json()
})
.then(({ data }) => {
console.log("FETCHED DATA...")
})
.catch((error) => {
console.error('ERROR: ', error)
})
console.log("DONE FETCHING...");
}
// A functional component that references signIn.
export const SignIn: React.FC<Props> = () => {
// irrelevant code ...
const onSubmit = (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
signIn(email, password, setAuthentication, setCurrentUser)
console.log("SIGNED IN...");
}
return <>A form here submits and calls onSubmit</>
}
This produces the following console log output:
SIGNING IN...
FETCHING...
DONE FETCHING...
SIGNED IN...
FETCHED DATA...
I want FETCHED DATA... to show up before DONE FETCHING.... I've tried playing around with aysnc/await but that's not working so I don't know where to go from here.
Just add another .then
.then((response) => {
return response.json()
})
.then(({ data }) => {
console.log("FETCHED DATA...")
return
}).then(()=> {
console.log("DONE FETCHING...");
})
.catch((error) => {
console.error('ERROR: ', error)
})
It would have to be in the then statements if you want the console.log to wait until the promise is resolved. Here's an example that uses async/await:
export const signIn = async (email:string, password:string) => {
console.log("FETCHING...");
const response = await fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
const data = await response.json();
console.log("FETCHED DATA...")
console.log("DONE FETCHING...");
}
You would also need to turn this into an async function if you want the console.log to happen after the data is done fetching:
const onSubmit = async (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
await signIn(email, password, setAuthentication, setCurrentUser)
console.log("SIGNED IN...");
}
In order to use async await, you need to return a promise from the call. So basically you don't execute the .then and wrap the call in a try catch block.
export const signIn = async (email:string, password:string) => {
console.log("FETCHING...");
return fetch(`${endPoint}/sign_in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
}
and
const onSubmit = async (e: CustomFormEvent) => {
e.preventDefault()
console.log("SIGNING IN...")
// calls my signIn function from above
// I don't want this to finish until the fetch inside it does.
try {
const data = await signIn(email, password, setAuthentication, setCurrentUser)
// Parse data, do something with it.
console.log("SIGNED IN...");
} catch (e) {
// handle exception
}
}
You may want to look more into how promises in JavaScript works.
One problem here is in signIn. What you're doing right now is:
function signIn() {
// 1. log FETCHING
// 2. call asynchronous fetch function
// 3. log DONE FETCHING
}
The key here is that fetch is asynchronous. The program doesn't wait for it to finish before moving on. See the problem? The JavaScript interpreter is going to run step 3 without waiting for step 2 to finish.
There are multiple ways to fix this. First, you can use then. Here's an example:
promise
.then(res => func1(res))
.then(res => func2(res))
.then(res => func3(res))
Here, you're telling JavaScript to:
Run promise, and wait for it to resolve.
Take the result from promise and pass it into func1. Wait for func1 to resolve.
Take the result from func1 and pass it into func2. Wait for func2 to resolve.
etc.
The key difference here is that you are running each then block in order, waiting for each previous promise to be resolved before going to the next one. (Whereas before you didn't wait for the promise to resolve).
Your code with promises would look like:
export const signIn = (email: string, password: string) => {
console.log("FETCHING...")
// Note that we return the promise here. You will need this to get onSubmit working.
return fetch(/* args */)
.then(res => res.json())
.then(({ data }) => console.log("DONE FETCHING"))
.catch(err => /* HANDLE ERROR */)
}
The second way to fix this is to use async and await. async and await is simply syntax sugar over promises. What it does underneath is the exact same, so make sure you understand how promises work first. Here's your code with async and await:
// The async keyword here is important (you need it for await)
export const signIn = async (email: string, password: string) => {
console.log("FETCHING...");
try {
const res = await fetch(/* args */) // WAIT for fetch to finish
const { data } = res.json()
console.log("FETCHED DATA...")
} catch (err) {
/* HANDLE ERROR */
}
console.log("DONE FETCHING...")
}
There's also a second similar problem in onSubmit. The idea is the same; I'll let you figure it out yourself (the important part is that you must return a Promise from signIn).

Categories