How to get onUploadProgress value in an await function from axios? - reactjs

I'm relatively new to react and having trouble getting the progress value "progressEvent" of axios in the onUploadProgress callback,
I have two files, one for the api call and one for my react component:
Here is a sample of my api.js
function uploadImage(file) {
return axios.post('/api/media_objects', file, {
onUploadProgress: progressEvent => {
let percentComplete = progressEvent.loaded / progressEvent.total
percentComplete = parseInt(percentComplete * 100);
console.log(percentComplete);
}
}).then(response => response.data.id);
}
and my try/catch from the component
try {
const upload = await xxxAPI.uploadImage(formData);
} catch (error) {
console.log(error);
}
How can i retrieve in the "try" the "percentComplete" ?
Thanks !

Generally, I'd advise using some kind of state management (redux/mobx) for controlling this flow. Not to handle it directly from a React component. So the component will trigger a kind of action and the upload process will be handled outside.
But, for a very simple solution, you'd need something like this:
function uploadImage(file, updateProgress) {
return axios.post('/api/media_objects', file, {
onUploadProgress: progressEvent => {
let percentComplete = progressEvent.loaded / progressEvent.total
percentComplete = parseInt(percentComplete * 100);
console.log(percentComplete);
updateProgress(percentComplete);
}
}).then(response => response.data.id);
}
const MyComponent = () => {
const [progress, setProgress] = useState(0);
const onUpload = useCallback(() => {
myApi.uploadImage(data, setProgress);
},[]);
return <div>
<span>Uploaded: {progress}</span>
<button onClick={onUpload}>Upload</button>
</div>;
};
the progress value is stored in the component's state so it can be updated and rendered.
I would also put the try/catch in the API method rather than in the component.
On a more general note. I'd advise using a library such as react-uploady to manage the upload for you. There's are a lot of edge cases and functionality you need to handle typically when uploading files and a small 3rd party like Uploady takes care of it for you: Preview with progress for file uploading in React

Related

Calling my Custom Fetch Components Directly Without Having to use a useState based variable

I am still learning React JS, so, I dont know if this is the way React JS works (so, I am doing it the right way), or I am doing the wrong way, and hence need some help.
I am using a three layer network abstraction for making fetch calls.
First, here is my raw network call making component.
export function useFetch(uri: string, something?: string, getThetoken?: any,
setrequesttype?: string,body? : Object,triggerapi? : string) {
const [data, setData] = useState();
const [error, setError] = useState();
const [loading, setLoading] = useState(true);
//by default we assume a GET request
var requesttype = "GET";
if (setrequesttype !== undefined) {
requesttype = setrequesttype;
}
useEffect(() => {
if (!uri) return;
if (something === "authorized" && !getThetoken) return;
fetch(uri, {
method: requesttype,
headers: {
Authorization: `Bearer ${getThetoken}`,
}
}
)
.then(data => data.json())
.then(setData)
.then(() => setLoading(false))
.catch(setError);
}, [uri, something, getThetoken,requesttype,triggerapi]);
return {
loading,
data,
error
};
}
Here is my middle layer for networking. A middleman between components and the above useFect network calling component.
const Fetch: FC<FetchProps> = ({
uri,
renderSuccess,
loadingFallback = <p>---</p>,
renderError = (error: any) => (
<div>
</div>
),
something,
getThetoken,
setrequesttype,
body,
triggerapi
}: FetchProps) => {
console.log("inside Fetch");
const { loading, data, error } = useFetch(uri, something, getThetoken,setrequesttype,body,triggerapi);
if (loading) return loadingFallback;
if (error) return renderError(error);
if (data) return renderSuccess({ data });
}
export default Fetch;
I built these components, and they work just fine. However, I do run into issues, like this. Here is a component that is able to successfully use it.
const RandomQuote = () => {
const [something, setsomething] = useState("hello");
function changeSomething() {
if (something === "bird") {
setsomething("hello");
} else {
setsomething("bird");
}
}
return (
<Fragment>
<RandomQuoteHelper something={something} />
<Button color="primary" onClick={changeSomething}>
{buttondisplaystring}
</Button>
</Fragment>
);
};
export default RandomQuote;
and here is the RandomQuoteHelper which makes the call to the api, and renders the result.
function RandomQuoteHelper({ something }: RandomQuoteHelperProps) {
//TODO - these things should be coming from a config file
const baseURL = "https://localhost:44372";
const endPoint = "/api/UserNotLoggedIn/GetHoldOfthem";
var url2 = baseURL + endPoint;
//manually set the request type
const setrequesttype = "GET";
return (
<Fetch
uri={url2}
renderSuccess={QuoteDetails}
something={something}
setrequesttype = {setrequesttype}/>
);
}
The above code works, and I get to call my API just fine. Unfortunately, I have to use the function 'changeSomething' to force a state change, which then calls the Fetch/useFetch components.
In the current form, the button click cannot directly call my network components. Not unless a dependent value is forcibly changed. I built it this way, by using a react js book. The author lady, built it this way, which suited her example perfectly. However, now, as I work on an actual project, it does not seem like the best approach. That means, throughout my project, anytime i want to trigger a api call, I have to keep using this 'changeSomething' type setup.
So, is there any way, I can directly call my fetch component?

React onSubmit e.preventDefault() not working sometimes + Axios

Right now I'm facing this strange problem in React where the onSubmit function with e.preventDefault() does avoid refreshing the page sometimes, and sometimes it doesn't.
I've created two hooks to keep track of the uploaded files and their progress bars.
const [ uploadedFiles, setUploadedFiles ] = useState([]);
const [ uploadPercentages, setUploadPercentages ] = useState([]);
The onSubmit function uses Axios to make a request to the backend.
const onSubmit = async e => {
e.preventDefault();
if(!!file) {
// Show file box on screen
let index = addUpload(file);
const formData = new FormData();
formData.append("file", file);
try {
await axios.post("/upload_video", formData, {
onUploadProgress: progressEvent => {
const { loaded, total } = progressEvent;
let progress = uploadPercentages.concat(0)
progress[index] = Math.round((loaded * 100) / total)
setUploadPercentages(progress);
}
})
} catch(err) {
// Handlers
}
}
return false // trying something different to avoid refresh
}
Just in case, the addUpload function has shown little to no relation with the source of the problem. I took it away to test it and things behave the same way.
If anyone can help I would appreciate it.
Edit:
Here is the form.
<form className="choose-file" onSubmit={onSubmit}>
<div className="file-container">
{ file ?
<p> { file.name } </p>
:
<label className="file-label" htmlFor="customFile">
<input type="file" className="file-input" id="customFile" onChange={ onChange }/>
<p><i className="fas fa-cloud-upload-alt"></i></p>
<p>Click here to select file</p>
</label>
}
</div>
<div className="file-options">
<input type="submit" value="Upload" className="file-input" id="customFile"/>
<button type="button" onClick={ removeFile }>Delete</button>
</div>
</form>
"file" is a third hook just to show the user the name of the file they just selected.
Edit 2:
The problems seems to appear only when file sizes are greater than 100MB. Besides, once the problem shows up, it starts to happen to every single file no matter its size.
For instance if I upload a 7MB file, page does not refresh, if I then try a 100MB file it starts refreshing for every upcoming file and all console logs after the axios post are never seen again.
Edit 3:
Since I'm running a local backend on Flask, I tried disconnecting it from the React app to see what happens. For small files the request to the backend is only asked once and the alarm inside the catch(err) triggers. For big files the request is asked around four times and it never reaches the catch part.
Send help
I guess the problem in not with addUpload but with the async. You can try this way
const onSubmit = (e) => {
e.preventDefault();
// After collecting formData ...
// in the *try* block do this
const post = axios.post("/upload_video", formData, {
onUploadProgress: progressEvent => {
const { loaded, total } = progressEvent;
let progress = uploadPercentages.concat(0)
progress[index] = Math.round((loaded * 100) / total)
setUploadPercentages(progress);
}
post.then(() => {alert('Done posting')}) // inside this you can do whatever you want to do after axios.post
}
**EDIT : **
Since you mentioned in the comment that the solution does not work, here's an alternative.
Declare a new function
const async post = () => {
// Get the formData here
await axios.post("/upload_video", formData, {
onUploadProgress: progressEvent => {
const { loaded, total } = progressEvent;
let progress = uploadPercentages.concat(0)
progress[index] = Math.round((loaded * 100) / total)
setUploadPercentages(progress);
}
}
// Now in the onSubmit function
const onSubmit = (e) => {
e.preventDefalut();
post()
}
This is the final solution I have. Hope this works...
I've been facing problems with using arrow functions as HANDLERS,
try turning it into normal function
async function HandelOnSubmit (e) {
e.preventDefault();
if(!!file) {
// Show file box on screen
let index = addUpload(file);
const formData = new FormData();
formData.append("file", file);
try {
await axios.post("/upload_video", formData, {
onUploadProgress: progressEvent => {
const { loaded, total } = progressEvent;
let progress = uploadPercentages.concat(0)
progress[index] = Math.round((loaded * 100) / total)
setUploadPercentages(progress);
}
})
} catch(err) {
// Handlers
}
}
return false // trying something different to avoid refresh }
At the end, the problem turned out to be something as simple as saving the files in the React's public folder.
My guess is that this caused the whole project to refresh every time a change is detected inside this folder. For small objects the time it takes almost zero, but for big files it takes a while, causing the components to refresh as well.

FileReader - how to update local state after store is updated?

I'm playing around with a food recognition api.
I have a component with a local state called ingredients.
In the component, I have an input tag that accepts a file image upload and calls cameraHandler method onChange. The method uses FileReader to convert the image into Base64
Once the FileReader is finished encoding the image, the method calls a redux action fetchIngredientsFromImage to post the base64 image into a route to trigger to trigger an API call (to analyze the ingredients in the image).
The response is sent back to the front end, and used to update store.
So basically, the API call is successful, I get the data I need, and store is updated successfully. Great.
But what I also need to do, is update my local ingredients state. But I don't know how to wait for store to be updated before calling setState.
I've tried componentDidUpdate with if(this.props !== prevProps) methodToUpdateLocalState(), but this doesn't work because for some reason the component won't re-render after store is updated.. Turns out that everything inside componentDidUpdate runs first, and store is updated afterwards. I feel like also isn't necessary (probably).
I also tried .then the awaited readers inside cameraHandler, but .then is undefined.
I'd appreciate any input I could get. Really at a loss here, because I have the data, and I just need to somehow grab it so I can setState.
Component
class RecipesSearch extends Component {
state = {
ingredients: [], //need to update this after store is updated, but how?
};
cameraHandler = async (event) => {
const { fetchIngredientsFromImage } = this.props;
const file = event.target.files[0];
const reader = new FileReader();
await reader.readAsDataURL(file);
reader.onloadend = async () => {
const imgBase = reader.result.replace(/^data:image\/(.*);base64,/, '');
await fetchIngredientsFromImage(imgBase); //.then here is undefined
};
};
render(){
<input
className="form-check-input"
type="file"
name="camera"
accept="image/*"
onChange={this.cameraHandler}
/>
}
Actions
const fetchIngredientsFromImage = (imgBase) => async (dispatch) => {
const { data } = await axios.post(`/api/camera/`, { imgBase });
return dispatch(setIngredientsFromCamera(data)); //successfully updates store
};
as a workaround I made an axios.post call inside cameraHandler. Not proud of it, because I'd like to utilize store and keep it consistent with my other methods, but for the time being it'll do I guess.
cameraHandler = async (event) => {
// const { loadIngredientsFromImage } = this.props;
const file = event.target.files[0];
const reader = new FileReader();
await reader.readAsDataURL(file);
reader.onloadend = async () => {
const imgBase = reader.result.replace(/^data:image\/(.*);base64,/, '');
await axios
.post(`/api/camera/`, { imgBase })
.then((response) => this.setState({ ingredients: response.data }));
};
};

Value of state variable is lost - React

I want to build a CRUD in React with Laravel and Firebase. Everything is perfect when I'm working with text, but I got trouble when I try to upload an image to Firebase Storage. I can save it but I can't get its URL.
I wrote 2 "console.log". In the first one the URL is there, but the second one (when I try to get the URL from the state variable) doesn't return anything.
handleSubmit = event =>{
event.preventDefault();
const {imagen} = this.state;
if(imagen!=null){
const uploadTask = storage.ref(`imagenes/${imagen.name}`).put(imagen);
uploadTask.on('state_changed',
(snapshot) => {
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
this.setState({progress});
},
(error) => {
console.log(error);
},
() => {
storage.ref('imagenes').child(imagen.name).getDownloadURL().then(url => {
this.setState({url});
console.log(this.state.url); //<<<<<<<<<<<<<SHOW URL (IT'S OK!)
})
});
}
var direccion = null;
const form = event.target;
let data = new FormData(form);
data.append('url', this.state.url);
console.log(this.state.url); //<<<<<<<DOESN'T SHOW URL !! (HERE'S THE TROUBLE)
If you want to check the entire file:
https://github.com/AndresVasquezPUCE/project/blob/master/pelicula
I'm not a professional, so please don't be rude :D
this.setState is asynchronous
If you want to get the updated state value, add a callback and access the new state there like
this.setState({ url: 'some url'}, () => {
conosle.log(this.state.url);
});
Data is loaded from Firebase asynchronously. By the time your console.log(this.state.url); //<<<<<<<DOESN'T SHOW URL !! (HERE'S THE TROUBLE) the data hasn't been loaded from Firebase yet, and the then hasn't been called yet.
Any code that needs the data from Firebase needs to either be inside the then() callback (such as console.log(this.state.url); //<<<<<<<<<<<<<SHOW URL (IT'S OK!)) or be called from there (such as this.setState({url})).

React Redux Firebase Upload File Object

Trying to pass the file object to redux action and perform the function inside of an redux action, not sure its the correct way? but basically i want back downloadURL from firebase upload complete so I can show image front end.
createLocation(event) {
event.preventDefault();
const fileObject = this.state.file;
const test = {
fileObject
}
this.props.uploadImage_func(test);
}
and action function:
export function uploadImage_func(fileObject) {
return dispatch => {
const fileName = 'myimage';
const storageRef = firebase.storage().ref('test/' + fileName);
const task = storageRef.put(fileObject);
task.on('state_changed',
function complete(snapshot) {
const downloadURL = task.snapshot.downloadURL;
},
).then(function () {
dispatch(attemptLogin({
...downloadURL
}));
});
}
}
error:
As you can see you have got an error Invalid argument in 'put' at index 0: Expected Blob or File. So first of all you need path exactly File or Blob. If you did right in you createLocation and got file object than you need not to wrap it in const test object more. That action causes unnecessary nesting, so just path fileObject as it is. And more. When you subscribe for firebase UploadTask on event you need to path callback functions and do it in a right order, so try to use next:
uploadTask.on('state_changed',
(snapshot) => {
// here you could log loading information in percents but uploading is not finished yes
console.log((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
},
(error) => console.log(error),
() => {
// And after uploading is complete you could get your download url
console.log('Call save img', uploadTask.snapshot.downloadURL);
}
);
For more information read documentation for Firebase Storage (Upload files)

Resources