I have the React code below. I want integrate the handleUpdate into the handleUpload function in a synchronous way so that the state is set before the rest of the function is executed. I tried, the following below, with my edit in bold, but it appears to be executing asynchronously and not setting state before executing. Can anybody please instruct me where i need to make a change to meet my needs?
handleUpdate = event => {
this.setState({
selectedFile: event.target.files[0]
})
}
handleUpload = () => {
**this.handleFileChange;**
var fd = new FormData();
fd.append('file', this.state.selectedFile, this.state.selectedFile.name);
fetch('/upload', {method: 'POST', body: fd})
.then(response => {
return response.json()})
.then(result => {
console.log(result)})
.catch(function(error) {
console.log("ERROR:" + error);
});
}
handleUpdate = event => {
this.setState({
selectedFile: event.target.files[0]
}, ()=>this.handleUpload())
}
You can call the handleUpload function inside the callback function of setState, to make sure that the handleUpload function gets called only after the state.selectedFile is set
Related
I've been fighting with this code for days now and I'm still not getting it right.
The problem:
I'm working with a form that has a dropzone. On submit handler, I need to save the images' url in an array, but it's always returning as an empty array.
Declaring images array:
const [images, setImages] = useState([]);
Here I get the images' url and try to save them in the array:
const handleSubmit = () => {
files.forEach(async(file)=> {
const bodyFormData = new FormData();
bodyFormData.append('image', file);
setLoadingUpload(true);
try {
const { data } = await Axios.post('/api/uploads', bodyFormData, {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${userInfo.token}`,
},
});
setImages([...images,data])
setLoadingUpload(false);
} catch (error) {
setErrorUpload(error.message);
setLoadingUpload(false);
}
})
}
Here I have the submitHandler function where I call the handleSubmit():
const submitHandler = (e) => {
e.preventDefault();
handleSubmit();
dispatch(
createCard(
name,
images,
)
);
}
I know it's because of the order it executes the code but I can't find a solution.
Thank you very much in advance!!!!
Issue
React state updates are asynchronously processed, but the state updater function itself isn't async so you can't wait for the update to happen. You can only ever access the state value from the current render cycle. This is why images is likely still your initial state, an empty array ([]).
const submitHandler = (e) => {
e.preventDefault();
handleSubmit(); // <-- enqueues state update for next render
dispatch(
createCard(
name,
images, // <-- still state from current render cycle
)
);
}
Solution
I think you should rethink how you compute the next state of images, do a single update, and then use an useEffect hook to dispatch the action with the updated state value.
const handleSubmit = async () => {
setLoadingUpload(true);
try {
const imagesData = await Promise.all(files.map(file => {
const bodyFormData = new FormData();
bodyFormData.append('image', file);
return Axios.post('/api/uploads', bodyFormData, {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${userInfo.token}`,
},
});
}));
setImages(images => [...images, ...imagesData]);
} catch(error) {
setErrorUpload(error.message);
} finally {
setLoadingUpload(false);
}
}
const submitHandler = (e) => {
e.preventDefault();
handleSubmit();
}
React.useEffect(() => {
images.length && name && dispatch(createCard(name, images));
}, [images, name]);
To prevent race-conditions, you could try to use the setImages with the current value as follows:
setImages(currentImages => [...currentImages, data])
This way, you will use exactly what is currently included in your state, since the images might not be the correct one in this case.
As another tip, instead of looping over your files, I would suggest you map the files, as in files.map(.... With this, you can map all file entries to a promise and at the end, merge them to one promise which contains all requests. So you can simply watch it a bit better.
Just await your map function with an await Promise.all() function. This will resolve all promises and return the filled array
I have the following code in my React class component.
For some reason, I am observing that, inside componentDidMount, despite having the keyword await before the call to this.getKeyForNextRequest(), the execution is jumping to the next call, this.loadGrids().
Am I doing something wrong here?
async componentDidMount() {
await this.getKeyForNextRequest();
await this.loadGrids();
}
getKeyForNextRequest = async () => {
const dataRequester = new DataRequester({
dataSource: `${URL}`,
requestType: "POST",
params: {
},
successCallback: response => {
console.log(response);
}
});
dataRequester.requestData();
}
loadGrids = async () => {
await this.loadGrid1ColumnDefs();
this.loadGrid1Data();
await this.loadGrid2ColumnDefs();
this.loadGrid2Data();
}
You can try using the Promise constructor:
getKeyForNextRequest = () => {
return new Promise((resolve, reject) => {
const dataRequester = new DataRequester({
dataSource: `${URL}`,
requestType: "POST",
params: {},
successCallback: response => {
console.log(response);
resolve(response);
}
});
});
}
This ensures you're waiting for a relevant promise, one that resolves only upon successCallback completing, rather than one that resolves instantly to undefined as you have it currently.
This is called "promisifying" the callback.
If DataRequester offers a promise-based mode, use that instead of promisifying the callback.
I am trying to setState after an API call, and I know this is an async task but I can't figure out how to update my state. My code looks like this:
loadUserDetails = () => {
this.setState({
isLoading: true,
status: "Fetching user details..."
}, () => {
fetch('url', { method: 'Get', credentials: 'include' })
.then(res => res.json())
.then((data) => {
console.log("results")
console.log(data.name);
console.log(data.surname);
console.log(data.emailAddress);
this.setState({
userProfile: data
})
if (this.state.userProfile != null)
this.loadRolesData();
})
})
});
.catch(console.log);
}
The console logs are producing the correct values but when I try to update the userProfile to data it doesn't happen. Reading the docs I can see useEffect as a solution but unsure how to implement it.
Edit:
I am initiating this from componentDidMount(). I think this is the correct place but happy to be told otherwise.
I think you did the task in the wrong order.
Do fetch for the api, afterwards do setState. Here's one simple example.
fetch(...).then(res => {
this.setState({...})
})
Please don't get confused about the second parameter of setState, that is to wait till state to finish update. Normally that is designed for some special occasion, 99% of time you don't need that.
setState doesn't update the state immediately after the call, and so that's why there is a second argument (callback). It is fired only when the update is finished. You used that second argument in your first setState call actually. So you can either do the same thing in the second call:
this.setState({
isLoading: true,
status: "Fetching user details..."
}, () => {
fetch('url', { method: 'Get', credentials: 'include' })
.then(res => res.json())
.then((data) => {
console.log("results")
console.log(data.name);
console.log(data.surname);
console.log(data.emailAddress);
this.setState({
userProfile: data
}, () => {
// this code will get fired only after the state updates
if (this.state.userProfile != null) {
this.loadRolesData();
}
})
})
.catch(console.log);
});
Or you can use react hooks which would require you to refactor your component into a function and rewrite your fetch logic like the following:
const [userProfile, setUserProfile] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(false);
// This function will get fired every time the userProfile state updates
React.useEffect(() => {
if (userProfile != null) {
loadRolesData();
}
}, [userProfile]);
const loadUserProfile = () => {
setIsLoading(true);
setStatus("Fetching user details...");
fetch('url', { method: 'Get', credentials: 'include' })
.then(res => res.json())
.then((data) => {
console.log("results")
console.log(data.name);
console.log(data.surname);
console.log(data.emailAddress);
setUserProfile(data);
setIsLoading(false);
})
.catch(console.log);
};
My question is about correctly implementing an async function to fetch data. I've a function called _getData() and I'm calling it on the componentDidMount() of a screen. But when server response is slow, switching to this screen is getting slower. So I would like to use async function for fetching data. But I'm not sure if I'm doing it correctly. Is that a correct approach? I can't be sure if it works async or not.
Here is my Api._getData() code:
const _getData = async () => {
return await axios.get("http://blabla.com/someservice", { params: someParamDataHere });
};
export const Api = {
_getData
};
and on SomeScreen.js, I also have loadData() function which calls the function above and does state updates.
loadData() {
Api._getData()
.then((response) => {
this.setState({ myData: response.data });
})
.catch((error) => {
console.log(error.response);
});
}
in componentDidMount() function of the same screen I'm calling this loadData() function directly.
Now, is it enough to declare Api._getData() as async and using await in it, or should I change some trigger functions too?
Thank you very much for your help.
instead of async await use promises
export const getRequest = (url) => {
return new Promise((resolve, reject) => {
api
.get(url)
.then((response) => {
handleReponse(response)
.then((errorFreeResponse) => {
resolve(errorFreeResponse);
})
.catch((error) => {
reject(error);
});
})
.catch((error) => {
reject(handleError(error));
});
});
};
You are doing correct while retrieving in load Data . What you can do more is try more syntactical sugar of es6 by using async await in loadData , hence
loadData = async() =>{
try{
let response = await Api._getData();
this.setState({ myData: response.data });
} catch(err){
console.log(error.response);
}
}
Hope it helps. feel free for doubts
Hi I am trying to call an api assign the returned values to a state object in React, the API is returning values but the values are not being set to state, not understanding what's the reason thank you
handleDDLCommunityChange = event => {
let filesFromApi = []; // ["file1", "file2", "file3", "file4"];
fetch('https://localhost:44352/api/files/Community-1')
.then((response) => {
return response.json();
})
.then(data => {
filesFromApi = data.map(file => { return { value: file, display: file } });
}).catch(error => {
console.log(error);
debugger;
});
console.log(filesFromApi);
this.setState({
files: filesFromApi.map(file => {
return {
fileName: file,
checked: false
};
})
});
};
fetch is an async method. An async method dispatches an action with the callbacks and unblocks following code branch from executing. The callbacks are then used to act on completion (success or failure) of the async method execution.
As you are calling the setState outside of the callbacks of the fetch call's chain, it's not guaranteed to run after the fetch call is done. As Sudheer has pointed out in their comment, you should try to set the state in a then block of the fetch chain.
warning: untested code
handleDDLCommunityChange = event => {
let filesFromApi = []; // ["file1", "file2", "file3", "file4"];
fetch('https://localhost:44352/api/files/Community-1')
.then(response => response.json())
.then(data => {
filesFromApi = data.map(file => ({ value: file, display: file });
this.setState({
files: filesFromApi.map(file => ({
fileName: file,
checked: false
})
})
});
}).catch(error => {
console.log(error);
debugger;
});
};