Which one is executed first Fetch or setState? - javascript

I was trying to send data from frontend(React) to backend(express) from a HTML form, and clear the fields after submit. This is what it looks like. Here item and amount are state of controlled components. So my question is as I'm updating state before fetch its not updating it. As fetch and setState are async so which one will get executed first. Even if I write state updates after fetch then also it seems like fetch is executed first. But I'm not getting how ?
function onSub(e, setList, list) {
e.preventDefault();
setList([...list, { item, amount }]);
setItem("");
setAmt("");
fetch("http://localhost:3001/addData", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title: item, amt: amount }),
})
.then((res) => res.text())
.then((data) => {
console.log(data);
});
}

They're in a race. Normally you'd expect the state to get set first, since the state setters are operating just locally within the DOM and the fetch has to go talk to a server, but they're in a race and you can't count on which one is going to win the race.
If you want to control which happens first, you need to do that explicitly. It's easier to make the fetch finish first since you can just wait to call the state setters until the end of the fetch process. That also gives you the opportunity to handle a fetch failure (transient network error, for instance) without losing the information the user provided because you've already cleared it from state:
function onSub(e, setList, list) {
e.preventDefault();
fetch("http://localhost:3001/addData", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ title: item, amt: amount }),
})
.then((res) => {
if (!res.ok) { // This check was missing
throw new Error(`HTTP error ${res.status}`);
}
return res.text();
})
.then((data) => {
console.log(data);
setList([...list, { item, amount }]);
setItem("");
setAmt("");
})
.catch(error => {
setError(/*...some error message saying something went wrong...*/);
});
}
Side note: I explain the comment about the check that was missing here. To avoid having to write all that boilerplate, I suggest having a few handy utility functions lying around, for instance:
export async function postJSON(url, data) {
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (!res.ok) { // This check was missing
throw new Error(`HTTP error ${res.status}`);
}
return res;
}
Then the code would look like this:
function onSub(e, setList, list) {
e.preventDefault();
postJSON("http://localhost:3001/addData", { title: item, amt: amount })
.then((data) => {
console.log(data);
setList([...list, { item, amount }]);
setItem("");
setAmt("");
})
.catch(error => {
setError(/*...some error message saying something went wrong...*/);
});
}

Related

How can I able to fetch air table record against field name?

I'm using Airtable db to fetch data against an input.
I want to fetch data from database against that input but I'm not aware how to achieve that.
Here's my fetch API call function
const fetchData = async e => {
e.preventDefault();
fetch(`https://api.airtable.com/v0/${process.env.AIRTABLE_BASE_ID}/${process.env.AIRTABLE_TABLE_NAME}`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.API_KEY}`,
},
})
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong');
}
})
.then(rawResponse => {
console.log(rawResponse);
})
.catch(error => {
customToast.error('Something went wrong.');
console.log('Error', error);
});
};
The Airtable API has two different GET methods. One will return a list of records and if you don't include any query parameters, it will eventually give you all records for the specified table. The other GET method takes an Airtable Record ID and returns just that record.
One of the query parameters for the list GET request is called filterByFormula. You can provide a valid Airtable formula that evaluates to true/false and the API will only return records for which the formula evaluates to true.
One of the easiest ways is to simply compare the field in Airtable against the value you are interested in like:
{My Field Name}='My Field Value'
The GET request would look something like:
fetch(
`https://api.airtable.com/v0/${process.env.AIRTABLE_BASE_ID}/${process.env.AIRTABLE_TABLE_NAME}?filterByFormula={My Field Name}='My Field Value'`,
{
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.API_KEY}`,
},
}
)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong');
}
})
.then(rawResponse => {
console.log(rawResponse);
})
.catch(error => {
customToast.error('Something went wrong.');
console.log('Error', error);
});
For more complex comparisons, Airtable does support a variety of text comparison functions like FIND and it also has functions for handling regular expressions.
This will return a list of records, even if there is only one record in the list. If the field you are comparing to might contain duplicate values, then you need to handle the situation where the API response contains more than one record and decide what to do.

Refactor from fetch to await that can yield same result

So I moved over a non-reusable fetch request code snippet to my API:
let response = await fetch(visitURL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + userJWT
},
body: JSON.stringify(endingVisit)
});
if (response.ok) {
let {visitId, createdAt} = await response.json();
const viewVisitDto = new ViewVisitDto(`${visitId}${createdAt}${visitorId}${doctorId}${oldPatientId}`);
return viewVisitDto;
} else {
throw new Error("deactivated!")
}
I was able to get this far:
axios.post(visitURL, {
headers,
body: JSON.stringify(visit)
}).then((response) => {
console.log(response);
}).catch((error) => {
console.log(error);
})
But does not exactly give me the visitId and createdAt from the response and I cannot use a response.ok nor a response.json(). Essentially I need to pull out that visitId and createdAt that should be coming back in the response.
I also tried just using node-fetch library, but although in VS code it seems to accept it, TypeScript is not happy with it even when I do install #types/node-fetch and even when I create a type definition file for it, my API just doesn't like it.
Guessing what you are after is
// don't know axios, but if it returns a promise await it
const dto = await axios.post(visitURL, {
headers,
body: JSON.stringify(visit)
}).then((response) => {
// parse response
return {resonse.visitId, resonse.createdAt}
}).then(({visitId, createdAt}) => {
// form dto (where are other vals)?
return new ViewVisitDto(`${visitId}${createdAt}${visitorId}${doctorId}${oldPatientId}`);
}).catch((error) => {
console.log(error);
})
However - you don't mention where doctorId and oldPatientId come from... You try providing more info, including output of the console.log's and the surrounding code

Fetch won't return any results

I have never used fetch before, and have followed the documentation, however, no results are being returned from my backend. When I submit the form, the url changes, and all appears fine in my console, but no response from my express backend.
The following code is what I have after my form in a script tag. Can someone please advise?
async function getSample(url = `http://localhost:3000/lookup/${url}`, data = {}) {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
return response.json();
}
document.getElementById('search').addEventListener('submit', function(e) {
event.respondWith(
new Response(myBody, {
headers: {'Content-Type': 'application/json'}
})
);
});
You could try creating a promise and then handling the value returned by the fetch with resolve and reject
async function getSample(url = `http://localhost:3000/lookup/${url}`, data = {}){
return new Promise(function (resolve, reject) {
fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}).then(async response => {
if (response.ok) {
response.json().then(json => resolve(json));
} else {
response.json().then(json => reject(json));
};
}).catch(async error => {
reject(error);
});
});
};
You would then call it like
getSample(...)
.then(results => {
//Code when fetch is successful
}.catch(error => {
//Code when fetch fails
};
I think the problem with it returning nothing is that getSample is an async function, but I imagine you're calling it within a program that isn't async, and so whatever code comes after getSample is using trying to use the value returned from getSample, but nothing's been returned yet, so it's using an empty value. Either that or the return of getSample is happening before the fetch completes. I'm not sure of the exact order that things happen, but a promise should fix your problem

How do I post JSON data into deeper layers within my API?

This week I have learned to fetch data from an API with javascript and jQuery.
Until now, I've only had to fetch from deeper levels within objects (which I've succeeded at), but I still don't know how to post to specific elements within other objects.
I'm currently working on a smart home project, where I'm the one responsible for the web application.
All device controllers have got a 'favourite' button, which is the one that triggers this function to either favourise or un-favourise the pressed object:
function toggle_favourite(id) {
fetch('../../api/objects?id=' + id)
.then(response => response.json())
.then(data => {
if (data.objects[id-1].favourite == true) {
// set .favourite to 'false'
put(id, {
favourite: false
})
} else {
// set .favourite to 'true'
put(id, {
favourite: true
})
}
})
})
}
function put(id, data) {
fetch('../../api/objects?id='+id, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
}
The data that I'm trying to change is this favourite value.
How do I manouver over to this 'favourite' value with fech/'PUT'?
If the value isn't top level, you have to fetch the entire object, change the part you want to and then 'PUT'/'POST' the object at the end.
For this example, I fetched the entire object and saved it into a 'const', went through it and changed the 'favourite' value, and at the end I 'PUT' everything back into the object like so:
async function getObject(id){
const response = await fetch('../../api/objects?id='+id)
return response.json()
}
async function saveObject(){
const data = await getObject(int_id)
$.each(data, function(index, objects){
$.each(objects, function(index, values){
if (values.favourite == true ){
values.favourite = false
}
else{
values.favourite = true
}
})
})
put(int_id, data)
function put(id, data) {
fetch('../../api/objects?id='+id, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => response.json())
.then(data => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
})
}
}

How to get Readable error response from JavaScript Fetch API?

I am working on Reactjs redux on front-end and Rails API as a back-end.
So now I call API with Fetch API method but the problem is I cannot get readable error message like what I got inside the network tabs
this is my function
export function create_user(user,userInfoParams={}) {
return function (dispatch) {
dispatch(update_user(user));
return fetch(deafaultUrl + '/v1/users/',
{
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
method: "POST",
body: JSON.stringify(userInfoParams)
})
.then(function(response) {
console.log(response);
console.log(response.body);
console.log(response.message);
console.log(response.errors);
console.log(response.json());
dispatch(update_errors(response));
if (response.status >= 400) {
throw new Error("Bad response from server");
}
})
.then(function(json){
console.log("succeed json re");
// We can dispatch many times!
// Here, we update the app state with the results of the API call.
dispatch(update_user(json));
});
}
}
But when errors came I cannot figure out how to get readable response message like I got when I check on my browser network tabs
So this is what I got from the network tabs when I got errors.
My console
This is my rails code
def create
user = User.new(user_params)
if user.save
#UserMailer.account_activation(user).deliver_now
render json: user, status: 201
else
render json: { errors: user.errors }, status: 422
end
end
But I cannot find out how can I get that inside my function
Since the text is hidden inside promise within response object, it needs to be handled like a promise to see it.
fetch(bla)
.then(res => {
if(!res.ok) {
return res.text().then(text => { throw new Error(text) })
}
else {
return res.json();
}
})
.catch(err => {
console.log('caught it!',err);
});
Similar to your answer, but with a bit more explanation... I first check if the response is ok, and then generate the error from the response.text() only for the cases that we have a successful response. Thus, network errors (which are not ok) would still generate their own error without being converted to text. Then those errors are caught in the downstream catch.
Here is my solution - I pulled the core fetch function into a wrapper function:
const fetchJSON = (...args) => {
return fetch(...args)
.then(res => {
if(res.ok) {
return res.json()
}
return res.text().then(text => {throw new Error(text)})
})
}
Then when I use it, I define how to handle my response and errors as needed at that time:
fetchJSON(url, options)
.then((json) => {
// do things with the response, like setting state:
this.setState({ something: json })
})
.catch(error => {
// do things with the error, like logging them:
console.error(error)
})
even though this is a bit old question I'm going to chime in.
In the comments above there was this answer:
const fetchJSON = (...args) => {
return fetch(...args)
.then(res => {
if(res.ok) {
return res.json()
}
return res.text().then(text => {throw new Error(text)})
})
}
Sure, you can use it, but there is one important thing to bare in mind. If you return json from the rest api looking as {error: 'Something went wrong'}, the code return res.text().then(text => {throw new Error(text)}) displayed above will certainly work, but the res.text() actually returns the string. Yeah, you guessed it! Not only will the string contain the value but also the key merged together! This leaves you with nothing but to separate it somehow. Yuck!
Therefore, I propose a different solution.
fetch(`backend.com/login`, {
method: 'POST',
body: JSON.stringify({ email, password })
})
.then(response => {
if (response.ok) return response.json();
return response.json().then(response => {throw new Error(response.error)})
})
.then(response => { ...someAdditional code })
.catch(error => reject(error.message))
So let's break the code, the first then in particular.
.then(response => {
if (response.ok) return response.json();
return response.json().then(response => {throw new Error(response.error)})
})
If the response is okay (i.e. the server returns 2xx response), it returns another promise response.json() which is processed subsequently in the next then block.
Otherwise, I will AGAIN invoke response.json() method, but will also provide it with its own then block of code. There I will throw a new error. In this case, the response in the brackets throw new Error(response.error) is a standard javascript object and therefore I'll take the error from it.
As you can see, there is also the catch block of code at the very end, where you process the newly thrown error. (error.message <-- the error is an object consisting of many fields such as name or message. I am not using name in this particular instance. You are bound to have this knowledge anyway)
Tadaaa! Hope it helps!
I've been looking around this problem and has come across this post so thought that my answer would benefit someone in the future.
Have a lovely day!
Marek
If you came to this question while trying to find the issue because response.json() throws "Unexpected token at position..." and you can't find the issue with the JSON, then you can try this, basically getting the text and then parsing it
fetch(URL)
.then(async (response) => {
if (!response.ok) {
const text = await response.text()
throw new Error(text)
}
// Here first we convert the body to text
const text = await response.text()
// You can add a console.log(text), to see the response
// Return the JSON
return JSON.parse(text)
})
.catch((error) => console.log('Error:', error))
.then((response) => console.log(response))
I think you need to do something like this
export function create_user(user,userInfoParams={}) {
return function (dispatch) {
dispatch(update_user(user));
return fetch(deafaultUrl + '/v1/users/',
{
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
method: "POST",
body: JSON.stringify(userInfoParams)
})
.then(function(response) {
console.log(response);
console.log(response.body);
console.log(response.message);
console.log(response.errors);
console.log(response.json());
return response.json();
})
.then(function(object){
if (object.errors) {
dispatch(update_errors(response));
throw new Error(object.errors);
} else {
console.log("succeed json re");
dispatch(update_user(json));
}
})
.catch(function(error){
this.setState({ error })
})
}
}
You can access the error message with this way:
return fetch(deafaultUrl + '/v1/users/',
{
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
method: "POST",
body: JSON.stringify(userInfoParams)
})
.then(function(response) {
console.log(response);
console.log(response.body);
console.log(response.message);
console.log(response.errors);
console.log(response.json());
dispatch(update_errors(response));
if (response.status >= 400) {
throw new Error("Bad response from server");
}
})
.then(function(json){
console.log("succeed json re");
// We can dispatch many times!
// Here, we update the app state with the results of the API call.
dispatch(update_user(json));
})
// here's the way to access the error message
.catch(function(error) {
console.log(error.response.data.message)
})
;
The best choice is not to catch the error in the fetch because this will be useless:
Just in your api put a response with not code error
static GetInvoicesAllData = async (req,res) =>
{
try{
let pool = await new Connection().GetConnection()
let invoiceRepository = new InvoiceRepository(pool);
let result = await invoiceRepository.GetInvoicesAllData();
res.json(result.recordset);
}catch(error){
res.send(error);
}
}
Then you just catch the error like this to show the message in front end.
fetch(process.env.REACT_APP_NodeAPI+'/Invoices/AllData')
.then(respuesta=>respuesta.json())
.then((datosRespuesta)=>{
if(datosRespuesta.originalError== undefined)
{
this.setState({datosCargados:true, facturas:datosRespuesta})
}
else{ alert("Error: " + datosRespuesta.originalError.info.message ) }
})
With this you will get what you want.
You variables coming back are not in response.body or response.message.
You need to check for the errors attribute on the response object.
if(response.errors) {
console.error(response.errors)
}
Check here https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
You should actually be returning an error response code from the server and use the .catch() function of the fetch API
First you need to call json method on your response.
An example:
fetch(`${API_URL}`, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(userInfoParams)
})
.then((response) => response.json())
.then((response) => console.log(response))
.catch((err) => {
console.log("error", err)
});
Let me know the console log if it didn't work for you.

Categories