Firebase image uploads two times - reactjs

I am trying to create gallary app and when i try to upload image in fire storage there is only one image but in fire store there are two entries for same image so i think it is happening because it uploads image two times i try to figure out but nothing is working
Here is my use storage hook
import React, { useState, useEffect } from 'react'
import { fireStorage, fireStore } from '../firebase-config';
import { collection, addDoc, serverTimestamp } from "firebase/firestore";
import { ref, getDownloadURL, uploadBytesResumable } from "firebase/storage";
export default function useStorage(file) {
const [progresspercent, setProgresspercent] = useState(0);
const [error, setError] = useState(null);
const [url, setImgUrl] = useState(null);
useEffect(() => {
const storageRef = ref(fireStorage, `files/${file.name}`);
const uploadTask = uploadBytesResumable(storageRef, file);
uploadTask.on("state_changed",
(snapshot) => {
const progress =
Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
setProgresspercent(progress);
},
(error) => {
setError(error);
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
setImgUrl(downloadURL)
addDoc(collection(fireStore, 'images'),{
url: downloadURL,
createdAt: serverTimestamp()
})
});
}
);
},[file]);
return { progresspercent, url, error};
}
Here is my upload form
import { useState } from "react";
import ProgressBar from './ProgressBar'
function UploadForm() {
const [file, setFile] = useState(null);
const [error, setError] = useState("");
const allowedType = ["image/png", "image/jpg", "image/jpeg"];
const changeHandler = (e) => {
e.preventDefault()
let selectedFile = e.target.files[0]
if (selectedFile && allowedType.includes(selectedFile.type)){
setFile(selectedFile);
setError('')
}else{
setFile(null);
setError("Please select an image file");
}
};
return (
<form>
<label>
<input type="file" onChange={changeHandler} />
<span>+</span>
</label>
<div className="output">
{error && <div className="error">{error}</div>}
{file && <div>{file.name}</div>}
{file && <ProgressBar file={file} setFile={setFile} />}
</div>
</form>
);
}
export default UploadForm;
and Here is my progress bar
import React, { useEffect } from "react";
import useStorage from "..//../src/Hooks/useStorage";
export default function ProgressBar({ file, setFile }) {
const { url, progresspercent } = useStorage(file);
useEffect(() => {
if (url) {
setFile(null);
}
}, [url, setFile]);
return (
<div
className="progress-bar"
style={{ width: progresspercent + "%" }}
></div>
);
}
I don't have any other component, i try to fix it but i don't know why it is happening at first place.

Related

I am struggling to figure out what's wrong with this image upload component using React and Firebase

When I upload the image, the file is detected, but it does not load to firebase. I have noticed that while an upload is running and I navigate to another page within the app, the code starts running again.
In my console, I see:
Image upload started
Upload is 0% done
Upload is running
Nothing happens after this. For context, I have used the similar upload logic in other places within my app and it works.
Here is what my code currently looks like:
I have tried leaving out uuidv4 and the image upload still does not start.
import React, { useEffect, useState, useRef } from "react";
import { ref, getDownloadURL, getStorage, listAll,
uploadBytesResumable, deleteObject }
from 'firebase/storage'
import { auth, db, storage } from '../../firebase'
import { collection, doc, updateDoc, setDoc, getDoc, onSnapshot, deleteDoc } from '#firebase/firestore'
import { onAuthStateChanged } from "firebase/auth";
import { v4 as uuidv4} from "uuid";
import './ImagePopup.css'
const ImagePopup = (props) => {
const [imageFile, setImageFile] = useState("")
const [percentage, setPercentage] = useState(null)
const [data, setData] = useState({})
// const [imageID, setImageID] = useState(getImageID())
const [progress, setProgress] = useState(0)
useEffect(() => {
console.log(imageFile)
const name = imageFile.name + uuidv4()
const storageRef = ref(storage, `website images/${name}`)
console.log(storageRef)
const uploadTask = uploadBytesResumable(storageRef, imageFile)
console.log(uploadTask)
const uploadImageFile = () => {
console.log("image upload started");
uploadTask.on(
'state_changed',
(snapshot) => {
console.log(snapshot)
const progress = Math.round(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done')
setProgress(progress) //not setPercentage has been changed to setProgress
switch (snapshot.state) {
case 'paused':
console.log('Upload is paused');
break;
case 'running':
console.log('Upload is running');
break;
default:
break;
}
console.log(data.img)
},
(error) => {console.log(error)},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
setData((prev) => ({...prev, img:downloadURL}))
});
}
);
}
imageFile && uploadImageFile()
}, [imageFile])
return (
<div className="ImagePopupDiv">
<form >
<label htmlFor="websiteImageUpload" className="websiteImageUploadButton">upload image</label>
<input type="file" id="websiteImageUpload"
onChange={(e) => {
setImageFile(e.target.files[0])
}}
style={{ display: "none"}} required>
</input>
<img src={imageFile ? URL.createObjectURL(imageFile) : "https://icon-library.com/images/no-image-icon/no-image-icon-0.jpg"}
id="uploadedImage1"
alt="uploaded"
/>
<progress className="progressBar" value={percentage} max="100"/>
</form>
<button disabled={percentage !== null && percentage < 100} className='imagePopupSubmitButton' type='submit'>submit</button>
</div>
)
}
export default ImagePopup

TypeError: weatherData.map is not a function

I'm trying to map over data from API, but while writing the code to display the data I got this error: TypeError: weatherData.map is not a function
I tried removing useEffect from the code and tried to add curly brackets: const [weatherData, setWeatherData] = useState([{}])
Update: Line 14 log undefined : console.log(weatherData.response)
import axios from 'axios'
import { useEffect, useState } from 'react'
import './App.css'
function App() {
const [search, setSearch] = useState("london")
const [weatherData, setWeatherData] = useState([])
const getWeatherData = async () => {
try {
const weatherData = await axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${search}&appid={APIKEY}`);
console.log(weatherData.response);
if (weatherData) {
setWeatherData(weatherData);
}
} catch (err) {
console.error(err);
}
}
useEffect(() => {
getWeatherData()
}, [getWeatherData])
const handleChange = (e) => {
setSearch(e.target.value)
}
return (
<div className="App">
<div className='inputContainer'>
<input className='searchInput' type="text" onChange={handleChange} />
</div>
{weatherData.map((weather) => {
return (
<div>
<h1>{weather.name}, {weather.country}</h1>
</div>
)
})}
</div>
)
}
export default App
You're having errors in fetching the data as well as rendering it.
Just change the entire App component like this :
import { useEffect, useState } from "react";
import axios from "axios";
function App() {
const [search, setSearch] = useState("London");
const [weatherData, setWeatherData] = useState([]);
const APIKEY = "pass your api key here";
useEffect(() => {
const fetchData = async () => {
const result = await axios(
`https://api.openweathermap.org/data/2.5/weather?q=${search}&appid=${APIKEY}`
);
setWeatherData(result.data);
};
fetchData();
}, [search]);
const handleChange = (e) => {
setSearch(e.target.value);
};
return (
<div className="App">
<div className="inputContainer">
<input className="searchInput" type="text" onChange={handleChange} />
</div>
<h1>
{" "}
{weatherData.name} ,{" "}
{weatherData.sys ? <span>{weatherData.sys.country}</span> : ""}{" "}
</h1>
</div>
);
}
export default App;
this should be working fine just make sure to change : const APIKEY = "pass your api key "; to const APIKEY = "<your API key> ";
this is a demo in codesandbox
Create a promise function:
const getWeatherData = async () => {
try {
const weatherData = await axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${search}&appid={APIKEY}`);
console.log(weatherData.response);
if (weatherData.response.data) {
setWeatherData(weatherData.response.data);
}
} catch (err) {
console.error(err);
}
}
Then call it.

Why can't i import my component to another component in React JS?

I'm trying to import my ProgressBar component to my UploadForm component but it isn't working. The console announce mistake as: "./src/components/UploadForm.js
Module not found: Can't resolve './components/ProgressBar' in 'C:\Users\Admin\react-website\src\components'". I don't know what wrong? Can anybody help me? Thank you so much!
This is my ProgressBar.js:
import React from "react";
import useStorage from "../hooks/useStorage";
const ProgressBar = () => {
return ( <
div className = "progress-bar" > progress < /div>
)
};
export default ProgressBar;
This is my UploadForm.js:
import React, { useState } from "react";
import ProgressBar from "./components/ProgressBar";
const UploadForm = () => {
const [file, setFile] = useState(null); //to begin with we dont select a file
const types = ["image/png", "image/jpeg"];
const [error, setError] = useState(null);
const changeHandler = (e) => {
let selected = e.target.files[0];
if (selected && types.includes(selected.type)) {
setFile(selected);
setError("");
} else {
setFile(null);
setError("pls select an image file (png or jpeg)");
}
};
return (
<form>
<input type="file" onChange={changeHandler} />{" "}
<div className="output">
{" "}
{error && <div className="error"> {error} </div>}{" "}
{file && <div> {file.name} </div>}{" "}
{file && <ProgressBar file={file} setFile={setFile} />}{" "}
</div>{" "}
</form>
);
};
export default UploadForm;
This is my useStorage.js:
import {
useState,
useEffect
} from 'react'
import {
projectStorage
} from "../firebase/config"
const useStorage = (file) {
const [progress, setProgress] = useState(0)
const [error, setError] = useState(null)
const [url, setURL] = useState(null)
//going to fire everytime the dependency(file) changes
useEffect(() => {
//references
const storageRef = projectStorage.ref(file.name)
storageRef.put(file).on('state_changed', (snap) {
let percentage = (snap.bytesTransferred / snap.totalBytes) * 100
setProgress(percentage)
}, (err) => {
setError(error)
}, async() => {
const url = await storageRef.get.DownloadURL()
setUrl(url)
})
}, [file]);
return {
progress,
url,
error
}
}
export default useStorage;
Directory:
import ProgressBar from "./components/ProgressBar";
The . means the current directory, and UploadForm.js is in the src/components directory. So this line is trying to access src/components/components/ProgressBar, which doesn't exist. Change it to:
import ProgressBar from './ProgressBar";

How to use custom hook>

I am uploading an image and title for that image to firebase. I have made a custom hook(useStorage) which is used for uploading image and title for that image. I have two separate components UploadForm and ProgressBar, I am passing title and selected image and title from UploadForm to ProgressBar. I am able to make fields for image and title in firestore, image is getting uploaded but title field is remaining empty string.
I am attaching code for 3 files, any help would be appreciated.
import { useState, useEffect } from 'react';
import { projectStorage, projectFirestore, timestamp } from '../firebase/config';
const useStorage = (file) => {
const [progress, setProgress] = useState(0);
const [error, setError] = useState(null);
const [url, setUrl] = useState(null);
const [title, setTitle] = useState('');
useEffect(() => {
// references
const storageRef = projectStorage.ref(file.name);
const collectionRef = projectFirestore.collection('images');
storageRef.put(file).on('state_changed', (snap) => {
let percentage = (snap.bytesTransferred / snap.totalBytes) * 100;
setProgress(percentage);
}, (err) => {
setError(err);
}, async () => {
const url = await storageRef.getDownloadURL();
const createdAt = timestamp();
await collectionRef.add({ url, createdAt, title });
setUrl(url);
setTitle(title);
});
}, [file, title]);
return { progress, url, error, title };
}
export default useStorage;
import React, { useState } from 'react';
import ProgressBar from './ProgressBar';
const UploadForm = () => {
const [file, setFile] = useState(null);
const [error, setError] = useState(null);
const [header, setHeader] = useState('')
const types = ['image/png', 'image/jpeg'];
const handleChange = (e) => {
let selected = e.target.files[0];
if (selected && types.includes(selected.type)) {
setFile(selected);
setError('');
} else {
setFile(null);
setError('Please select an image file (png or jpg)');
}
};
return (
<form>
<label>
<input type="file" onChange={handleChange} />
<span>+</span>
</label>
<input placeholder="Enter title for pic..." type="text" name={header} onChange={(e)=> setHeader(e.target.value)} />
<div className="output">
{ error && <div className="error">{ error }</div>}
{ file && <div>{ file.name }</div> }
{ file && <ProgressBar file={file} setFile={setFile} header={header} /> }
</div>
</form>
);
}
export default UploadForm;
import React, { useEffect } from 'react';
import useStorage from '../hooks/useStorage';
import { motion } from 'framer-motion';
const ProgressBar = ({ file, setFile, header }) => {
const { progress, url, title } = useStorage(file);
useEffect(() => {
if (url) {
setFile(null);
}
}, [url, setFile]);
return (
<motion.div className="progress-bar"
initial={{ width: 0 }}
animate={{ width: progress + '%' }}
></motion.div>
);
}
export default ProgressBar;
useStorage have a [title, setTitle] hook which you use in
await collectionRef.add({ url, createdAt, title });
setUrl(url);
setTitle(title);
But the problem is that the title is empty. You never set a title. You pass the header to the progress bar, but you don't use it. You should update useStorage two take to arguments: file and header and call useStorage(file, header)
Update useStorage signature to const useStorage = (file, header) => {}
Inside useStorage you can update const [title, setTitle] = useState(header)
As far as I can tell you never get title from anywhere. All you do is send your state variable title which is initialized with '' to Firestore, then setTitle which that same empty string title.
Did you mean to pass header into your hook alongside file?

how to update old avatar image once new image uploaded without refresh

I don't know if I need to use global state like useContext for this ( I am not using redux in this project) but what I want to do is, once I have uploaded a new image it sends the image data back from the server and I set that state. I want to then replace the existing image on the screen with the newly uploaded one.
So, here is my file input component:
import React, { useState } from "react";
import ProgressBar from "../../../shared/components/progressBar/ProgressBar";
const UploadForm = () => {
const [file, setFile] = useState(null);
const [error, setError] = useState(null);
const types = ["image/png", "image/jpg", "image/jpeg"];
const changeHandler = (e) => {
let selected = e.target.files[0];
if (selected && types.includes(selected.type)) {
setFile(selected);
setError("");
} else {
setFile(null);
setError("Please select an image file(png or jpg");
}
};
return (
<form>
<input type="file" onChange={changeHandler} name="image"></input>
<div className="output">
{error && <div className="error">{error}</div>}
{file && <div>{file.name}</div>}
{file && <ProgressBar file={file} setFile={setFile} />}
</div>
</form>
);
};
export default UploadForm;
My progress bar component:
import React, { useEffect } from "react";
import useStorage from "../../hooks/use-storage";
import { motion } from "framer-motion";
import "./ProgressBar.css";
const ProgressBar = ({ file, setFile }) => {
const { url, progress } = useStorage(file);
useEffect(() => {
if (url) {
setFile(null);
}
}, [url, setFile]);
return (
<motion.div
className="upload-progress"
initial={{ width: 0 }}
animate={{ width: progress + "%" }}
></motion.div>
);
};
export default ProgressBar;
And, my upload custom hook. You can see here I am setting the state of the image url here but I don't know how to then update my Avatar component once the upload is complete.
import React, { useState, useEffect, useContext } from "react";
import Axios from "axios";
import { AuthContext } from "../context/auth-context";
const useStorage = (file) => {
const auth = useContext(AuthContext);
const [progress, setProgress] = useState(0);
const [error, setError] = useState(null);
const [url, setUrl] = useState(null);
useEffect(() => {
const formData = new FormData();
formData.append("image", file);
try {
const sendImage = async () => {
const response = await Axios.post(
"http://localhost:8000/api/v1/users/update-avatar",
formData,
{
headers: {
"Content-type": "multipart/form-data",
Authorization: "Bearer " + auth.token,
},
onUploadProgress: (progressEvent) => {
setProgress(
parseInt(
Math.round((progressEvent.loaded * 100) / progressEvent.total)
)
);
},
}
);
// get the new file name from the server so you can show it right away after upload
const { filename, path } = response.data.file;
setUrl({ filename, path });
};
sendImage();
} catch (err) {
setError(err);
console.log(err.response);
}
}, [file]);
return { progress, url, error };
};
export default useStorage;
Avatar component
import React, { useContext, useEffect, useState } from "react";
import Axios from "axios";
import { AuthContext } from "../../../shared/context/auth-context";
const Avatar = () => {
const auth = useContext(AuthContext);
const [avatar, setAvatar] = useState(null);
useEffect(() => {
const getAvatarImage = async () => {
const response = await Axios.get(
"http://localhost:8000/api/v1/users/get-avatar",
{ headers: { Authorization: "Bearer " + auth.token } }
);
setAvatar(response.data.avatar);
};
if (auth.token) getAvatarImage();
}, [auth.token]);
return (
<div>
{avatar && (
<img
src={`http://localhost:8000/uploads/images/${avatar}`}
width="200"
alt="avatar image"
/>
)}
</div>
);
};
export default Avatar;
Auth Context
import { createContext } from "react";
export const AuthContext = createContext({
isLoggedIn: false,
userId: null,
token: null,
email: null,
firstName: null,
login: () => {},
logout: () => {},
});

Resources