I believe my problem might have been answered more or less but I can't find a satisfactory response. I have a React form with an input and onChange lets you select a picture that immediately gets saved in the cloud. The cloud returns a response with an url and an id that I want to pass to the backend database, thus submitted with the form handleFormSubmit. The code below succeeds only if I wait until I get back the response from the cloud (I watch the Dev Tools Components). If I am to quick, the url is 'undefined' because the promise is not set. How can I programmatically properly chain these promises?
I use a state 'photoUrl' for the url and an object called 'Event'.
handling the file input onChangeand send to the cloud and get response back:
async function handleSendToCloudinary(e){
if (e.target.files[0]){
const formdata = new FormData()
formdata.append('photo', e.target.files[0])
fetch(CL_end_point, { method: 'POST', body: formdata})
.then(res=> res.json())
.then(data=> setPhotoUrl(data))
}
}
and the onSubmit on the global form:
async function handleFormSubmit(e){
e.preventDefault()
const formdata = new FormData()
if (photoUrl) {
formdata.append('url', await photoUrl.secure_url)
formdata.append('key', await photoUrl.product_id)
}
try {
setEvents(
await fetch(myEndPoint, { method: 'POST', body: formdata })
}...
I believe something around Promise.all might give a clue but I can't get the solution.
Something like the following fails ('object not iterable')
const p1 = new Promise((resolve)=> {
resolve(formdata.append('url', photoUrl.secure_url)
})
cont p2 = new Promise((resolve) => {
resolve(formdata.append('key', photoUrl.product_id)
})
Promise.all([p1,p2]).then(()=> {
setEvents(...)})
and the following fails too because of the state photoUrl update probably
const p1 = (formdata)=> {
formdata.append('url', photoUrl.secure_url)
return promise.resolve(formdata)
}
I think you need to introduce a loading flag to your state
const [loading, setLoading] = useState(false)
Then you set it to true when uploading your file:
async function handleSendToCloudinary(e){
if (e.target.files[0]){
setLoading(true)
const formdata = new FormData()
formdata.append('photo', e.target.files[0])
fetch(CL_end_point, { method: 'POST', body: formdata})
.then(res=> res.json())
.then(data=> setPhotoUrl(data))
.finally(() => setLoading(false))
})
}}
And then you disable your submit button while loading is true
As a last note, the complexity of your form handling is going to pile up as the time goes (certainly you'd want to handle errors in the future) so you might as well start looking up react state managers and their async handlers (redux and redux-thunk come to my mind first) in order to move your business-logic away from your components
Related
I'm trying to post a new object to my mongodb collection which is handled on my backend server, but the values from my request body are not passed to it.
I can guarantee that the object is created. The problem is that all the object values are empty.
I also guarantee that the nodejs code works correctly. The first thing I did was create a web application just using nodeJS and from that page I can create a new object normally.
But now I'm creating a frontend page using react and I'm not sure why my frontend page's post request doesn't work as intended
react code:
const handleSubmit = async (event) => {
event.preventDefault();
const requestOptions = {
method: 'POST',
mode: 'cors',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name:nome,
cnpj:cnpj,
email:email,
number:telefone,
seguradora:[seguradora],
index:765756
})
};
try{
const response = await fetch('http://localhost:3001/corretoras',requestOptions)
const data = await response.json()
console.log(data)
console.log(requestOptions.body)
}
catch(error){
console.log('error trying to post',error)
}
};
nodeJS code:
router.post('/',async(req,res)=>{
const lastIndex = await Corretora.find().sort({index:-1}).limit(1).index
const corretora = new Corretora({
name:req.body.name,
cnpj: req.body.cnpj,
email:req.body.email,
number:req.body.number,
seguradora:req.body.seguradora,
index: lastIndex
})
try{
const novaCorretora = await corretora.save()
res.redirect('corretoras/'+novaCorretora.id)
}
catch{
renderNewPage(res,corretora,true)
}
})
console log from react code:
Found my issue. I didn't type app.use(express.json()) in my server.js
I have a dispatch in one of my components that calls by form submission:
const handleSubmit = (values) => {
dispatch(resetPassActionCreator(values.email));
};
and this is resetPassActionCreator function:
export const resetPassActionCreator = username => dispatch => {
const url = PASSWORD_RESET_GET_EMAIL;
dispatch(
apiCallBegan({
url,
method: "POST",
onStart: passwordResetSlice.actions.resetPassword.type,
onSuccess: passwordResetSlice.actions.resetPasswordSuccess.type,
onError: passwordResetSlice.actions.resetPasswordFail.type,
data: {
username,
},
})
);
};
and I check the url and it does not have '/' at the end.
can anyone tell me why the browser sends get request instead of post?
This is not a problem with the browser.
Probably you are using apiCallBegan in the wrong way )
or its implementation does not support POST requests.
I am using Promise and axios in react to call POST api and fetch list of records.
Issue is when multiple API calls triggered then any one which response last is getting is updating state.
Where i want to use only last called API response.
Exp : call API 3 times with different postbody, in case first call response take time than 2nd & 3rd then callback is using response of 1st call to setstate, instead i want to forget 1 and second call and consider last call response only.
Following is example
Common File
const apiService = axios.create({
baseURL: 'https://example.com/api/,
});
function post(postData) {
return Promise.resolve(apiService.post('https://example.com/api/getuserlist', postData, {
headers: {'Authorization': `Bearer sdfsdfsdf-cvdfs`}
}));
}
Service File
static getUsers(postData) {
return post(postData);
}
React Component
function getUsersList = (Filters )=>{
getUsers({ "Filters": Filters }).then((response) => {
this.setState({ users: response.data})
})
}
Problem is when getUsersList get called multiple time whichever is last response is getting set to users state, where last call should be users list.
It's not yet possible to actually cancel promises in JavaScript (though there is a proposal for it).
However, it is possible to implement the functionality you want in React. Consider something like this:
// state shape:
// {
// loading: boolean
// apiPromise: null | Promise<{ data: User[] }>
// users: User[]
// }
getUsersList = async (Filters) => {
// raw promise - don't await it yet
const apiPromise = getUsers({ Filters })
this.setState({ apiPromise, loading: true })
// _here_ we await it
const response = await apiPromise
// check if it's the most recent API request we made
if (this.state.apiPromise === apiPromise) {
this.setState({ users: response.data, loading: false })
} else {
console.log("stale val discarded:", users)
}
}
CodeSandbox demo - simplified example with mocked API and single val rather than list of users. Try clicking the button many times in quick succession and watch the console output.
Using CPromise wrapper the code might look like this See the live demo:
const CPromise = require('c-promise2');
const axios= require('axios');
// Let's wrap axios get method to the CPromise
function get(url){
return new CPromise((resolve, reject, {onCancel})=>{
axios.get(url, {
cancelToken: new axios.CancelToken(function executor(cancel) {
onCancel(cancel)
})
}).then(resolve, reject);
});
}
let chain= null;
function makeRequest(url){
chain && chain.cancel();
chain= get(url).then((response)=> {
console.log(`Response ${JSON.stringify(response.data)}`);
}, function (err) {
console.warn(`Error: ${err}`);
}
);
}
// some endpoint with a delay of 3 seconds for a response
const url= "https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=3s";
makeRequest(url);
// make the same request again, abort the previous
setTimeout(()=> makeRequest(url), 1000);
But since the package is in early beta stage, you can use the plain cancellation token, provided by axios See Axios documentation
I'm using Cypress to do some API testing, but I am struggling to access values in the JSON response body; however I can perform assertions against the body which suggests it's receiving it correctly.
Below I am trying to assign the JSON body (response.body) and then get the value of 'id' out of it:
describe('Creating a board', () => {
it('should create a board', () => {
cy.request({
method : 'POST',
url:`${requestUrl}/boards/`,
qs: {
name : "test-board",
token : token,
key : key
}
}).then((response) => {
expect(response).property('status').to.equal(200)
expect(response.body).property('id').to.not.be.oneOf([null, ""])
const body = (response.body)
boardId = body['id']
})
})
I've done numerous searches and can't find a concrete way to do it. Any help would be appreciated...
I managed to solve this by using a Promise;
Doing some further reading, I found out the then function I am executing is synchronous (I'm new to JS, pls don't hurt me).
I refactored the then function to the following:
.then((response) => {
return new Promise(resolve => {
expect(response).property('status').to.equal(200)
expect(response.body).property('id').to.not.be.oneOf([null, ""])
const respBody = response.body;
boardId = respBody['id']
resolve(boardId)
})
It's probably not entirely correct or best practice, but it will do for my demo
Although not needed anymore as you found a workaround, I've looked into my cypress code. I was able to access properties of response body followingly:
cy.request({
...
}.its('body').then((body) => {
const whatever = body.whatever;
})
I believe it basically works the same as your workaround - waiting to resolve body in a promise.
I was able to do it in the following way:
cy.request(
'POST',
url,
payload()).then((response) => {
expect(response.body).to.have.property('ReturnCode', 'Success')
expect(response.body).to.have.property('ReturnText', 'Success')
expect(response.body).to.have.property('PaymentInstructionId')
paymentID = response.body.PaymentInstructionId
})
paymentID is the variable that is filled with the value that i want from the repply.
I have set up a form with file upload, where I upload an image to Cloudinary with separate request. What I want to do is to pick up an image url from the response Object from Cloudinary, pass it to my redux form and submit it all together with other form values to my server and database. So far my request looks like this
onChange(event) {
let formData = new FormData();
formData.append('file', event.target.files[0]);
formData.append('upload_preset', UPLOAD_PRESET);
axios({
url: `${COUDINARY_ROOT_URL}`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: formData
}).then((res) => {
console.log(res);
}).catch((err) => {
console.err(err);
});
My problem is, in what way can I add this image url data from response and pass it to my redux form before submitting it to my server?
If it is not an efficient way, would be really thankful for recommendations as well.
I believe redux form can only store state if you pass the data through a form component (e.g input tag, checkbox etc). But in this case you want to store the data to the store from a variable. I'm not aware of any means through redux form, so I recommend that you use redux instead.
Also, if you are saving the image url for the sole purpose of making another request to send both url and form data to your server and database, it is actually not necessary to save it in the redux store. You can just return it so that you can use it in the next promise chain, as shown in my code below.
But, if you still insist on saving it to the store perhaps for some other reason, what I would do is dispatch an action that saves the url in my redux store.
getImageUrl(formData) {
return axios({
url: `${COUDINARY_ROOT_URL}`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: formData
});
}
sendDataToServer(url, formData) {
// ... send url and formData to my server
}
onChange(event) {
let formData = new FormData();
formData.append('file', event.target.files[0]);
formData.append('upload_preset', UPLOAD_PRESET);
getImageUrl(formData)
.then(res => {
// assuming res.url contains the url and
// saveUrl is a method wrapped in dispatch from
// mapDispatchToProps
this.props.saveUrl(res.url);
// return res.url so that it can be used in the
// next then chain
return res.url;
})
.then(url => {
// we can get the url from previous promise chain
sendDataToServer(url, formData);
// ... rest of code
})
.catch(err => console.err(err))
}