Reputation: 253
I have react with redux app and Asp.NetCorewebApi
as Back-end . I want to upload file to firebase storage and get its URL to save in a database table so that I can display it.
The file is uploaded successfully but it does not setUrl
to save in state.
here is relevant part of UploadMaterial.jsx
component when input file is selected.
// component state variables
const[image,setImage]=useState(null);
const[url,setUrl]=useState('');
const[fileName,setFileName]=useState('');
// handlechange function when file is selected
const handleChange = e => {
const image = document.getElementById("uploadFile").files[0];
setFileName(image.name); //set filename
setImage(image); // set file
const uploadTask = storage.ref(`courseMaterial/${fileName}/`).put(image);
uploadTask.on('state_changed',
(snapshot) => {
// progress function ....
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
setProgress({ progress });
},
(error) => {
console.log(error);
},
);
() => {
// complete function ....
storage.ref(`courseMaterial`).child(`${fileName}`).getDownloadURL().then(url => {
setUrl(url);
console.log(url); // why url is not updated?
}) , (error) => {
// error function ....
console.log(error);
}
}
}
the file uploaded successfully but the URL is not updated so I am unable to save in state to send to my back-end database. Thanks in advance for help.
update:
here is the console.log and info screenshots.
[
here is the whole component
UploadMaterial.jsx
import React, { useState, useEffect } from "react";
import { Grid, TextField, withStyles, FormControl, InputLabel, Select, MenuItem, Button, FormHelperText } from "@material-ui/core";
import {useForm} from "../../_helpers";
import {storage} from '../../firebase';
import { useDispatch } from "react-redux";
import * as actions from "../../_actions/materialActions";
import { useToasts } from "react-toast-notifications";
import { Link,useParams,useHistory,useLocation } from 'react-router-dom';
import LinearProgress from '@material-ui/core/LinearProgress';
const styles = theme => ({
formControl: {
margin: theme.spacing(1),
minWidth: 300,
},
smMargin: {
margin: theme.spacing(1)
}
})
const initialFieldValues = {
title: '',
description: '',
postedBy: '',
userName: '',
courseId: '',
courseTitle: '',
type: '',
fileTitle: ''
};
const UploadMaterial = ({ classes, ...props }) => {
const[image,setImage]=useState(null);
const[url,setUrl]=useState('');
const[fileName,setFileName]=useState('');
const[preview ,setPreview]=useState(null);
const[progress,setProgress]=useState(0);
const dispatch = useDispatch();
//
function useQuery() {
return new URLSearchParams(useLocation().search);
}
let query = useQuery();
// let {fullName}=useParams();
let courseId=query.get("courseId");
let courseTitle=query.get("courseTitle");
let history = useHistory()
//
const { addToast } = useToasts();
//material-ui select
const inputLabel = React.useRef(null);
const [labelWidth, setLabelWidth] = React.useState(0);
React.useEffect(() => {
setLabelWidth(inputLabel.current.offsetWidth);
}, []);
//
const user=JSON.parse(localStorage.getItem('user'))
const handleChange = e => {
const image = document.getElementById("uploadFile").files[0];
setFileName(courseId+"-"+image.name);
setImage(image);
// setFileTitle(temp)
setPreview(e.target.files[0])
const uploadTask = storage.ref(`courseMaterial/${fileName}/`).put(image);
uploadTask.on('state_changed',
(snapshot) => {
// progrss function ....
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
setProgress({ progress });
},
(error) => {
// error function ....
addToast(" some error !check file size/type ", { appearance: 'error' })
console.log(error);
},
);
() => {
// complete function ....
storage.ref(`courseMaterial`).child(fileName).getDownloadURL().then(url => {
setUrl(url);
}) , (error) => {
// error function ....
console.log(error);
}
}
}
console.log("url:",url);
console.log("fileName:",fileName);
//validate()
//validate({title:'jenny'})
const validate = (fieldValues = values) => {
let temp = { ...errors }
if ('title' in fieldValues)
temp.title = fieldValues.title ? "" : "This field is required."
if ('type' in fieldValues)
temp.type = fieldValues.type ? "" : "This field is required."
if ('description' in fieldValues)
temp.description = fieldValues.description ? "" : "This field is required."
setErrors({
...temp
})
if (fieldValues == values)
return Object.values(temp).every(x => x == "")
}
const {
values,
setValues,
errors,
setErrors,
handleInputChange,
resetForm
} = useForm(initialFieldValues, validate)
///
useEffect(()=>{
setValues( { ...values, postedBy:user.id,userName:user.username,
courseId: courseId,courseTitle: courseTitle,
fileTitle:url})
},[values.title])
// for tinymce editor
console.log("values:",values);
//console.log("url:", url);
const onSuccess = () => {
resetForm()
addToast("uploaded successfully", { appearance: 'success' })
}
const handleSubmit = e => {
e.preventDefault()
if (validate()) {
addToast("working please wait !", { appearance: 'warning' })
// file upload code here
if(progress==100)
dispatch(actions.create(values, onSuccess))
}
}
console.log("progress:",progress);
return (
<form autoComplete="off" noValidate className={classes.root} onSubmit={handleSubmit}>
<hr/>
<input id="uploadFile" type="file" className={classes.formControl} onChange={handleChange}/><br/>
<br/><LinearProgress value={progress} color="primary" max="100" /><br/>
<TextField
placeholder="Enter Title in short"
name="title"
variant="outlined"
label="title"
value={values.title}
onChange={handleInputChange}
{...(errors.title && { error: true, helperText: errors.title })}
/>
<br/>
<FormControl variant="outlined"
className={classes.formControl}
{...(errors.type && { error: true })}
>
<InputLabel ref={inputLabel}>Material type</InputLabel>
<Select
name="type"
value={values.type}
onChange={handleInputChange}
labelWidth={labelWidth}
>
<MenuItem value="">Select type</MenuItem>
<MenuItem value="helpingfile">Helping File</MenuItem>
<MenuItem value="instructionfile">Instruction file</MenuItem>
</Select>
{errors.type && <FormHelperText>{errors.type}</FormHelperText>}
</FormControl><br/>
<TextField
placeholder="Enter short description"
name="description"
variant="outlined"
label="Description"
value={values.description}
onChange={handleInputChange}
{...(errors.description && { error: true, helperText: errors.description })}
/><br/>
<div>
<Button
variant="contained"
color="primary"
type="submit"
className={classes.smMargin}
>
Submit
</Button>
<Button
variant="contained"
className={classes.smMargin}
onClick={resetForm}
>
Reset
</Button>
<Button onClick={() => history.goBack()}
size="small" className={classes.smMargin} variant="contained" color="secondary">
back
</Button>
</div>
</form>
);
}
export default (withStyles(styles)(UploadMaterial));
Upvotes: 0
Views: 2590
Reputation: 11
Make sure you are useing useEffect hook as it is working asynchronous Here is my code :
const [imageUpload, setImageUpload] = React.useState(null); // image selecting state
const [image, setImage] = React.useState(""); //url setting state
const storage = getStorage();
useEffect(() => {
// declare the data getImage function
const getImage = async () => {
const ImageURL = await getDownloadURL(ref(storage, `${imageUpload.name}`));
setImage(ImageURL);
}
// call the function
getImage()
console.log(image)
}, [imageUpload])
const uploadImage = () => {
if (imageUpload == null) return;
const storageRef = ref(storage, `${imageUpload.name}`);
uploadBytes(storageRef, imageUpload).then((snapshot) => {
console.log("Uploaded image");
});
};
Upvotes: 0
Reputation: 26266
Cleaning up the indentation of your code, you notice the following problems:
uploadTask.on('state_changed',
(snapshot) => {
// progress function ....
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
setProgress({ progress });
},
(error) => {
console.log(error);
},
); // PROBLEM: this bracket & semi-colon closes off on()
() => { // <-- which means this function is floating and never actually called
// complete function ....
storage.ref(`courseMaterial`).child(`${fileName}`).getDownloadURL()
.then(url => {
setUrl(url);
console.log(url); // why url is not updated?
}), // PROBLEM: this comma is outside of then()
(error) => { // <-- meaning this error function is also floating and never called
// error function ....
console.log(error);
}
}
Fixing it, you get:
uploadTask.on('state_changed',
(snapshot) => {
// file upload progress report
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
setProgress({ progress });
},
(error) => {
// file upload failed
console.log(error);
},
() => {
// file upload completed
storage.ref(`courseMaterial`).child(`${fileName}`).getDownloadURL()
.then(
(url) => {
// got download URL
setUrl(url);
console.log(url);
},
(error) => {
// failed to get download URL
console.log(error);
}
);
}
);
You can avoid this in the future using a StorageObserver
instead of the three callback arguments to on()
.
This line:
on(firebase.storage.TaskEvent.STATE_CHANGED, nextCallback, errorCallback, completeCallback)
can be converted to:
on(firebase.storage.TaskEvent.STATE_CHANGED, {
next: nextCallback,
error: errorCallback,
complete: completeCallback
})
Which can be used like so:
uploadTask.on('state_changed', {
next(snapshot): {
// file upload progress report
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
setProgress({ progress });
},
error(error): {
// file upload failed
console.log(error);
},
complete(): {
// file upload completed
storage.ref(`courseMaterial`).child(`${fileName}`).getDownloadURL()
.then(
(url) => {
// got download URL
setUrl(url);
console.log(url);
},
(error) => {
// failed to get download URL
console.log(error);
}
);
}
});
Upvotes: 1