Fiaz Ahmed Ranjha
Fiaz Ahmed Ranjha

Reputation: 253

Upload File to firebase storage and get URL to save in a database in react

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. [screenshot infoscreenshot error 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

Answers (2)

Mahinur Rahman
Mahinur Rahman

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

samthecodingman
samthecodingman

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

Related Questions