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

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.

Related

Firebase Storage async request with "listAll" method. React

I'm getting an image from a user and storing it with in Cloud Storage through Firebase. The storing works fine.
Right after that I'm requesting all images from the storage to display them, but I got the response without the last submit. So I need to refresh the page, then useEffect make one more request and everything works.
UPD. Here's the the complete logic:
This function is uploading image to the storage:
const uploadImage = async (image,imageName) => {
if (image == null) return;
const imageRef = ref(storage,`images/${imageName}`);
try {
uploadBytes(imageRef, image);
} catch (err) {
alert(`${err}`)
}
}
This function does request to firestore, but doesn't return last uploaded image every time, just randomly. sometimes it does, usually not :
const getImages = async () => {
try {
const imagesListData = await listAll(imagesListRef);
setImagesList([]);
imagesListData.items.forEach(item => {
getDownloadURL(item).then(url => setImagesList(prev => [...prev,url]));
})
} catch(err){
alert(err.message);
}
}
after refreshing the page, useEffect does the job :
useEffect(() => {
getImages();
},[])
As I said above sometimes it works as I expected from the first try without me changing the code(which is the most confusing),most of the times I need to refresh the page to get the last image.
p.s. list() instead listAll() give same results

React playlist player - fetching data and storing the result problem

What I am trying to achieve
I am building a playlist player widget for my app using Soundcloud API. I am able to fetch the data but I am running into a design/technical issue when I need to implement the play/pause feature for a given track.
The GET method returns a JSON track (song) with multiple functions associated, such as response.play(), response.pause(), etc. Currently, my onClick() handle for pausing and playing the song fetches new data for the same track and overrides the current song, which is not what I want. I want to be able to play the song, the pause/play it without restarting.
JSFiddle (Wasn't able to implement StackOverflow JS/HTML/CSS snippet widjet)
[-------- Working JSFiddle here !!---------]
Playlist.tsx
const SC = require('soundcloud');
SC.initialize({
client_id: '1dff55bf515582dc759594dac5ba46e9'
});
const playIcon = 'https://www.pngfind.com/pngs/m/20-201074_play-button-png-image-background-circle-transparent-png.png';
const pauseIcon = 'https://thumbs.dreamstime.com/z/pause-button-icon-transparent-background-sign-flat-style-204042531.jpg';
async function getJson(trackId) {
let response = await SC.stream('/tracks/' + trackId);
return response;
}
const Playlist = () => {
const[playState, setPlayState] = React.useState(false);
const handlePlayClick = () => {
getJson(913128502).then((track) => {
track.setVolume(0.025); //increase if you don't hear background sound
playState ? track.pause() : track.play();
setPlayState(!playState);
});
}
return (
<img src={!playState ? playIcon : pauseIcon }
onClick={() => { handlePlayClick() } }
height="100" width="100" >
</img>
)
}
export default Playlist;
Things I have tried
I have looked at several SOs posts trying to understand how to store fetched data in a global variable. From my understanding, this can't quite be done because of synchronicity, I can only use the track GET response and its associated functions only inside the function
getJson(trackId).then((track) => {
// can only call track.play() or track.pause() locally here ?
});
To bypass this, I tried to do
let globalTrack;
getJson(trackId).then((track) => {
globalTrack = track;
});
globalTrack.play() // undefined error
I have further tried to use other tools like async/await but with no success.
I am at a loss for ideas and lost enough hair over this issue. Does anyone know how I can achieve what I want?
You can use useEffect() hook to get a track, it will fetch the track once when the component is first mounted.
https://jsfiddle.net/x7j0qwvh/20/
const [track, setTrack] = React.useState(null);
React.useEffect(() => {
musicFetch(913128502).then((music) => {
music.setVolume(0.025);
setTrack(music);
});
}, [])

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

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

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})).

Resources