What check did I miss that my edit page only works if it has a file selected?
I tried to make some conditionals in the onSubmit function but I haven't found the way yet.
I know it has to do with the image array because when I click edit without selecting a file I get this error:
TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
<script setup>
import { getAuth, onAuthStateChanged } from 'firebase/auth'
import {
getStorage,
uploadBytesResumable,
ref as firebaseRef,
getDownloadURL
} from 'firebase/storage'
import { doc, updateDoc, getDoc, serverTimestamp } from 'firebase/firestore'
import { db } from '../../firebase.config.js'
import { v4 as uuidv4 } from 'uuid'
const themeEditListing = ref([])
const formData = ref({
name: '',
})
const route = useRoute()
// get the theme id from the route
const themeId = route.params.id
const auth = getAuth()
onAuthStateChanged(auth, (user) => {
if (user) {
// set user on formData
formData.value.user = user.uid
} else {
navigateTo('/')
}
})
const fetchEditListing = async () => {
const docRef = doc(db, 'themes', themeId)
// response
const docSnap = await getDoc(docRef)
if (docSnap.exists) {
themeEditListing.value = docSnap.data()
formData.value = themeEditListing.value
}
}
fetchEditListing()
const onSubmit = async () => {
const newImgUrls = await Promise.all(
Array.from(formData.value.images).map((image) => {
return storeImage(image)
})
).catch((err) => {
console.log(err)
})
const imgUrls = newImgUrls ? formData.value.imgUrls.concat(newImgUrls) : formData.value.imgUrls
const formDataCopy = { ...formData.value, imgUrls, timestamp: serverTimestamp() }
delete formDataCopy.images
// update listing
// get doc reference
const docRef = doc(db, 'themes', themeId)
// update doc
await updateDoc(docRef, formDataCopy)
formData.value = { ...formData.value, imgUrls }
}
const onFileChange = (e) => {
const file = e.target.files
formData.value.images = file
}
// store images in firebase storage
const storeImage = async (image) => {
return new Promise((resolve, reject) => {
formData.value.imgUrls.push('')
newImgProgress.value = 1
const storage = getStorage()
const fileName = `${auth.currentUser.uid}-${image.name}-${uuidv4()}`
const storageRef = firebaseRef(storage, 'images/' + fileName)
const uploadTask = uploadBytesResumable(storageRef, image)
uploadTask.on(
'state_changed',
(snapshot) => {
newImgProgress.value = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
console.log('Upload is ' + newImgProgress.value + '% done')
switch (snapshot.state) {
case 'paused':
console.log('O Upload está parado')
break
case 'running':
console.log('Subindo a Imagem')
break
default:
break
}
},
(error) => {
reject(error)
},
() => {
// Handle successful uploads on complete
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
// pop the last element
formData.value.imgUrls.pop()
newImgProgress.value = 0
resolve(downloadURL)
})
}
)
})
}
</script>
<template>
<div>
<form #submit.prevent="onSubmit" class="w-full border shadow-md p-4 rounded-md">
<div>
<input
type="text"
#value="formData.name"
v-model="formData.name"
placeholder="Nome do Tema"
class="input w-full border-x-indigo-300"
/>
</div>
<div>
<input
type="file"
id="images"
#value="formData.images"
#change="onFileChange"
accept=".jpg, .png, .jpeg, .webp"
multiple
class="hidden"
/>
</label>
</div>
<button type="submit"">Edit Theme</button>
</form>
</div>
</template>
I edited the code as much as possible to make it short to read.
Related
I'm using getDownloadURL from fire storage to get a url for any image that is uploaded on to the server. But when I try to display the images in my img element source, it returns a blank icon instead of the actual image. The image even on firestorage has trouble rendering and when its opened, its just a white square instead of the image. The function is the sendImage function in the code. Ive also added CORS configuration to my google cloud shell and changed the meta data multiple times, and yes the images do have tokens and the rules are fine. Any help .
import React, { useContext, useEffect, useRef, useState } from 'react'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faPaperclip, faImage, faPaperPlane } from '#fortawesome/free-solid-svg-icons'
import { doc, db } from '../firebase'
import { arrayUnion, onSnapshot, setDoc, updateDoc } from 'firebase/firestore'
import ChatContext from '../Context/ChatContext'
import { AuthContext } from '../Context/AuthContext'
import { getStorage, ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
const Chat = () => {
const {user} = useContext(AuthContext)
const [messages, setMessages] = useState([])
const {data} = useContext(ChatContext)
const [file, setFile] = useState()
const [msg, setMsg] = useState()
const aref = useRef(null)
const scrollRef = useRef(null)
const photo = '013bfa05b2955d0bff2823c2f3304e1838ac30801c.jpg'
const storage = getStorage()
const storageRef = ref(storage, `images/${photo}`)
const metadata = {
contentDisposition: 'inline',
contentType: 'image/jpeg'
};
useEffect(() => {
const unSub = onSnapshot(doc(db, "messages", data.chatId), (doc) => {
doc.exists() && setMessages(doc.data().messages);
});
return () => {
unSub();
};
}, [data.chatId]);
useEffect(()=>{
scrollToBottom()
}, [messages])
const sendImage = async() =>{
const uploadTask = uploadBytesResumable(storageRef, file, metadata);
await uploadTask.on(
(error) => {
console.log(error)
},
async() => {
// Handle successful uploads on complete
// For instance, get the download URL: https://firebasestorage.googleapis.com/...
await getDownloadURL(uploadTask.snapshot.ref).then(async(downloadURL) => {
await updateDoc(doc(db, 'messages', data.chatId), {
messages: arrayUnion({photoURL: downloadURL})
})
console.log(downloadURL)
});
}
);
}
const handleChange = (e) =>{
setMsg({msg: e.target.value, origin: user.uid})
}
const sendMessage = async() =>{
if(data.chatId!='global'){
if(msg){
await updateDoc(doc(db, 'messages', data.chatId), {
messages: arrayUnion(msg)
})
}
}
if(file){
sendImage()
}
aref.current.value = ''
}
const handleFileSelect = (e) =>{
e.preventDefault()
setFile({photoURL: e.target.files[0], origin: user.uid})
}
const scrollToBottom = () =>{
scrollRef.current.scrollIntoView({behavior: 'smooth'})
}
const handleKey = (e) => {
e.code === "Enter" && sendMessage();
};
return (
<div className='chatWrapper'>
<button onClick={()=>sendImage()}>here</button>
<div className="cWrapper">
<div className="conversationProfile">
<img className='conversationImage' src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/1f/Dwayne_Johnson_2014_%28cropped%29.jpg/640px-Dwayne_Johnson_2014_%28cropped%29.jpg" alt=""/>
<span>DM Name</span>
</div>
<div className="messages">
{messages ? messages?.map((message) =>(
<div className={`message ${message.origin==user.uid && 'sender'}`}>{message.msg ? message.msg : <img className='imgMSG' referrerPolicy='no-referrer' src='https://firebasestorage.googleapis.com/v0/b/mwdims.appspot.com/o/images%2F013bfa05b2955d0bff2823c2f3304e1838ac30801c.jpg?alt=media&token=a95f8bd2-97fe-4bfa-a7d5-36772407c418"
'></img>}</div>
)) : <div className='placeHolder'>Say Something </div>}
<div ref={scrollRef} style={{visibility: 'hidden'}}></div>
</div>
<div className="chatBar">
<input ref={aref} type="text" placeholder='Type Something...' defaultValue='' onChange={handleChange} onKeyDown={handleKey}/>
<div className="chatFunctions">
<FontAwesomeIcon icon={faPaperclip} />
<input type='file' onChange={handleFileSelect}></input>
<FontAwesomeIcon icon={faImage}></FontAwesomeIcon>
<FontAwesomeIcon icon={faPaperPlane} onClick={()=>sendMessage()}/>
</div>
</div>
</div>
</div>
)
}
export default Chat
Firebase Collection : 'Data'. Within data you have the docIds.
As of now, I wrote this
const fetchProduct = async ()=> { // <------ async
const docId = window.location.href.split('/').pop();
console.log(docId,"Ok")
if(docId != ""){
console.log(docId)
let userDoc = await getDoc(doc(db, 'Notes', docId))
console.log(userDoc);
userDoc = userDoc.data().content; // <------ await
console.log(userDoc);
userDoc = userDoc.exists; // <------ exists not exists();
}
}
useEffect(async () => {
(async () => {
await fetchProduct()
})();
}, [window.location.href]); // Only run the effect if the url changes
Eg. If url is url :<main.com>/hhFQaRx0RaCCGokkFMz
It should fetch data from docId=hhFQaRx0RaCCGokkFMz , which can be then used to populated textBox ith id='MyBox1'
Error:
My full code of this section: error causing part in comment section
import React from "react"
import { db } from "../firebase-config";
import { useState,useEffect } from "react";
import {
collection,
getDoc,
doc
} from "firebase/firestore";
export default function Navbar(props){
const [textSearch, settextSearch] = useState('');
const handleGet = async(e)=>{
e.preventDefault();
try {
const snap = await getDoc(doc(db, 'Notes', textSearch.trim()))
if (snap.exists()) {
console.log(snap.data())
settextSearch(snap.data().content);
document.getElementById("myBox1").value = snap.data().content;
}
else {
console.log("No such document")
alert("No such document")
}
} catch(error) {
alert("Error")
console.log(error)
}
}
/*
const fetchProduct = async ()=> { // <------ async
const docId = window.location.href.split('/').pop();
console.log(docId,"Ok")
if(docId != ""){
console.log(docId)
let userDoc = await getDoc(doc(db, 'Notes', docId))
console.log(userDoc);
userDoc = userDoc.data().content; // <------ await
console.log(userDoc);
userDoc = userDoc.exists; // <------ exists not exists();
}
}
useEffect(async () => {
(async () => {
await fetchProduct()
})();
}, [window.location.href]); // Only run the effect if the url changes
*/
const handleOnChange = (event)=>{
// console.log("On change");
settextSearch(event.target.value)
}
return(
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<div className="container-fluid">
<a className="navbar-brand">{props.title}</a>
<form className="d-flex">
<input className="form-control mr-sm-2" value={textSearch} onChange={handleOnChange} type="search" placeholder="Search" aria-label="Search"/>
<button className="btn btn-outline-success my-2 my-sm-0" onClick={handleGet} type="submit">Search</button>
</form>
</div>
</nav>
);
}
Here is a file uploading component, everything works at expected, however, when attempting to POST the file using Axios in a useCallback, the ProgressBar component re-renders infinitely if there is an error from Axios. If I comment out the Axios post, the component does not re-render infinitely. How do I avoid the infinite re-rendering of the ProgressBar component?
import { useState, useCallback, useEffect } from 'react'
import { useDropzone } from 'react-dropzone'
import uuid from 'react-uuid'
import axios from 'axios'
import ProgressBar from './ProgressBar'
const FileUploader = ({ setNotifications }) => {
const [fileCount, setFileCount] = useState(0)
const [filesUploaded, setFilesUploaded] = useState([])
const [progress, setProgress] = useState(0)
const [uploaded, setUploaded] = useState(false)
const [exists, setExists] = useState(false)
const [error, setError] = useState(false)
const onDrop = useCallback(acceptedFiles => {
acceptedFiles.forEach(file => {
const reader = new FileReader()
// console.log(file)
reader.onloadstart = () => {
const exists = filesUploaded.find(uploadedFile => uploadedFile.name === file.name)
if (exists) {
return setNotifications(notifications => {
return [...notifications, `'${file.name}' has already been uploaded.`]
})
}
// setStart(true)
return setFilesUploaded(filesUploaded => {
return [...filesUploaded, file]
})
}
reader.onabort = () => {
setError(true)
console.log('file reading was aborted')
}
reader.onerror = () => {
setError(true)
console.log('file reading has failed')
}
reader.onprogress = e => {
// console.log('loaded', e.loaded)
// console.log('total', e.total)
if (e.lengthComputable) {
setProgress((e.loaded / e.total) * 100)
}
}
reader.onload = async () => {
// complete
await axios.post(
'/api/images',
{
file: reader.result
}
)
.then(res => {
if (res) {
setUploaded(true)
if (res === 200) {
// success
setExists(false)
} else if (res === 409) {
// already exists
setExists(true)
}
}
})
.catch(err => {
setError(true)
console.error(err)
})
}
reader.readAsArrayBuffer(file)
})
}, [filesUploaded, setNotifications])
const { getRootProps, getInputProps } = useDropzone({ onDrop, multiple: true })
useEffect(() => {
setFileCount(filesUploaded.length)
}, [setFileCount, filesUploaded, setNotifications])
return (
<div>
<div className='file-uploader'>
<div
className='file-uploader-input'
{...getRootProps()}
>
<input {...getInputProps()} />
<p>Upload Files</p>
</div>
</div>
<div className='progress-bar-container'>
{filesUploaded.map(file => {
return (
<ProgressBar
key={uuid()}
file={file}
progress={progress}
uploaded={uploaded}
exists={exists}
error={error}
/>
)
})}
</div>
</div>
)
}
export default FileUploader
The component re-renders because filesUploaded is modified in the callback each time and is listed as a dependency to the same callback. It looks like you wish to terminate the upload if the file already has been updated, but currently you only terminate the loadstart event handler. I suggest you move some of the functionality out from then loadstart event.
acceptedFiles.forEach(file => {
const exists = filesUploaded.find(uploadedFile => uploadedFile.name === file.name)
if (exists) {
setNotifications(notifications => {
return [...notifications, `'${file.name}' has already been uploaded.`]
})
} else {
const reader = new FileReader()
reader.onloadstart = () => {
return setFilesUploaded(filesUploaded => {
return [...filesUploaded, file]
})
}
[...]
Based on the tutorial, I have exactly the same code but i am having problem while uploading the image. Every time I add the image and click upload i get an error which says :
reference.ts:290 Uncaught TypeError: ref._throwIfRoot is not a function
at uploadBytesResumable$1 (reference.ts:290:1)
at uploadBytesResumable (api.ts:164:1)
at uploadFile (AddUser.jsx:22:1)
at AddUser.jsx:49:1
at commitHookEffectListMount (react-dom.development.js:23049:1)
at commitPassiveMountOnFiber (react-dom.development.js:24816:1)
at commitPassiveMountEffects_complete (react-dom.development.js:24781:1)
at commitPassiveMountEffects_begin (react-dom.development.js:24768:1)
at commitPassiveMountEffects (react-dom.development.js:24756:1)
at flushPassiveEffectsImpl (react-dom.development.js:26990:1)
This is the code i have to add a new user with their credentials and image:
import { useEffect, useState } from 'react';
import { doc, setDoc, addDoc, collection, serverTimestamp } from "firebase/firestore";
import { createUserWithEmailAndPassword } from "firebase/auth"
import { db, auth, storage } from '../../firebase';
import { ref, uploadString, getDownloadURL, uploadBytesResumable } from "firebase/storage";
const New = ({ inputs, title }) => {
const [file, setFile] = useState("");
const [data, setData] = useState({});
useEffect(() => {
const uploadFile = () => {
const name = new Date().getTime() + file.name;
const storageRef = (storage, file.name);
const uploadTask = uploadBytesResumable(storageRef, file);
uploadTask.on('state_changed',
(snapshot) => {
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done');
switch (snapshot.state) {
case 'paused':
console.log('Upload is paused');
break;
case 'running':
console.log('Upload is running');
break;
default: break;
}
},
(error) => {
console.log(error)
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
setData((prev) => ({ ...prev, img: downloadURL }))
});
}
);
}
file && uploadFile();
}, [file])
const handleInput = (e) => {
const id = e.target.id;
const value = e.target.value;
setData({ ...data, [id]: value })
}
const handleAddUser = async (e) => {
e.preventDefault();
try {
const res = await createUserWithEmailAndPassword(auth, data.email, data.password);
// Add a new user in collection
await setDoc(doc(db, "users", res.user.uid), {
...data,
timeStamp: serverTimestamp()
});
} catch (err) {
console.log(err)
}
}
return (
<div className="new">
<Sidebar />
<div className="newContainer">
<Navbar />
<div className="top">
<h1 className="title">{title}</h1>
</div>
<div className="bottom">
<div className="left">
<img src={file ? URL.createObjectURL(file) : "/images/default.jpg"} alt="" />
</div>
<div className="right">
<form onSubmit={handleAddUser}>
<div className="formInput">
<label htmlFor="file">Upload Image</label>
<input type="file" id="file" onChange={(e) => setFile(e.target.files[0])} />
</div>
{inputs.map((input) => (
<div className="formInput" key={input.id}>
<label>{input.label}</label>
<input id={input.id} type={input.type} placeholder={input.placeholder} onChange={handleInput} />
</div>
))}
<button type="submit">SEND</button>
</form>
</div>
</div>
</div>
</div >
)
}
export default New
This is what I have in my firebase.js file. I have initialized firebaseConfig and all the necessary steps to upload a document to the collection. Also i have removed all the values for the firebaseConfig for security purpose.
import {
initializeApp
} from "firebase/app";
import {
getAuth
} from "firebase/auth";
import { getFirestore } from "firebase/firestore";
import { getStorage } from "firebase/storage";
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: ""
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
export const auth = getAuth();
export const storage = getStorage(app);
I am trying to display images from Firebase storage in my React app.
I could get all images from Firebase Storage using by listall.
but I don't know how to display these on ui.
I tried to use map method but didn't work.
Also, I want to keep these display even if page is reload.
should I use localstrage or useEffect?
plz help me.
here is my code.
const PhotoButton = () => {
var storageRef = firebase.storage().ref("images");
const [image, setImage] = useState("");
const [imageUrl, setImageUrl] = useState([]);
const handleImage = event => {
const image = event.target.files[0];
setImage(image);
};
const onSubmit = event => {
event.preventDefault();
if (image === "") {
console.log(“error);
return;
}
const uploadTask = storage.ref(`/images/${image.name}`).put(image);
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
next,
error,
complete
);
};
const complete = () => {
storage
.ref("images")
.child(image.name)
.getDownloadURL()
.then(fireBaseUrl => {
setImageUrl(fireBaseUrl);
});
};
// Now we get the references of these images
storageRef.listAll().then(function(result) {
result.items.forEach(function(imageRef) {
// And finally display them
console.log(result.items);
displayImage(imageRef);
});
}).catch(function(error) {
alert(“error!”)
});
function displayImage(imageRef) {
imageRef.getDownloadURL().then(function(url) {
setImageUrl.push(url);
// TODO: Display the image on the UI
}).catch(function(error) {
// Handle any errors
});
}
return(
<div>
<h3 className={classes.addPhotoText}>Photos</h3>
<div className="addphoto">
<form onSubmit={onSubmit}>
<input type="file" onChange={handleImage} />
<Button type="submit" className={classes.PhotoButton}>Submit</Button>
</form>
</div>
<img src={imageUrl} />
{imageUrl.map((url) => (
<img src={url}/>
))}
</div>
);
}
export default PhotoButton
It worked with this code.
useEffect(() => {
const fetchImages = async () => {
let result = await storageRef.child('images').listAll();
let urlPromises = result.items.map(imageRef => imageRef.getDownloadURL());
return Promise.all(urlPromises);
}
const loadImages = async () => {
const urls = await fetchImages();
setFiles(urls);
}
loadImages();
}, []);
For me i needed to add some things to #via answer to get it to work. I hoop it can help others
Firebase config
import firebase from "firebase/app";
import "firebase/firestore";
import "firebase/storage";
import "firebase/auth";
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: "",
};
if (firebase.apps.length === 0) {
firebase.initializeApp(firebaseConfig);
}
const auth = firebase.auth();
const db = firebase.firestore();
const storage = firebase.storage();
export { db, storage, auth };
code
import { storage } from "../components/config/config";
// Get all the images from Storage
const [files, setFiles] = useState();
useEffect(() => {
const fetchImages = async () => {
let result = await storage.ref().child("Name Of Your Files Map in storage").listAll();
let urlPromises = result.items.map((imageRef) =>
imageRef.getDownloadURL()
);
return Promise.all(urlPromises);
};
const loadImages = async () => {
const urls = await fetchImages();
setFiles(urls);
};
loadImages();
}, []);
console.log(files);