How to retrieve content and its image from firebase using react js - javascript

I have managed to push content and image and the name of the image is also added to the database as a reference, and I have managed to retrieve only content. if anyone can help me out it will be great.
As you can see my Home.js code below
Home.js :
function Home(isAuth) {
const [postList, setPostList] = useState([]);
const [imageUrl, setImageUrl] = useState(undefined);
const postsCollectionRef = collection(db, "posts");
useEffect(() => {
const getPosts = async () => {
const data = await getDocs(postsCollectionRef);
setPostList(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
};
const getImage = async () => {
const storage = getStorage()
.ref('/' + `${post.cover}`) //name in storage in firebase console
.getDownloadURL()
.then((url) => {
setImageUrl(url);
})
}
getPosts();
})
}

Related

Firebase doc field shows an empty array after using useState() [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed last month.
I'm new to react and I'm trying to make an app, where user can upload multiple images at once, and those images get sent to the firebase storage, and urls of those images get stored in an array inside a doc, so that later on they can be accessed in a different page.
Here is a snippet of my code:
function CreateCarParts({isAuth}) {
const [images, setImages] = useState(null);
const [imageUrls, setImageUrls] = useState([]);
let navigate = useNavigate();
const postsCollectionRef = collection(db, "CarParts")
useEffect(() => {
if (!localStorage.getItem('isAuth')){
navigate("/login");
};
}, []);
const uploadImages = async () => {
if(images === null) return;
let uniqueFolderName = new Date().getTime().toString();
const imageList = Array.from(images);
for(let i = 0; i < imageList.length; i++) {
const image = imageList[i];
const imageLinkName = `carParts/${uniqueFolderName}/${image.name}`;
const imageRef = ref(storage, imageLinkName);
const snapshot = await uploadBytes(imageRef, image);
const url = await getDownloadURL(snapshot.ref);
setImageUrls((prev) => [...prev, url]);
}
alert("Images Uploaded");
};
const createPost = async () => {
await uploadImages();
await addDoc(postsCollectionRef, {imageUrls});
navigate('/carparts');
};
return (
<>
<Form.Control onChange={(event) => {setImages(event.target.files);uploadImages();}} type="file" multiple />
<Button onClick={createPost}> Submit </Button>
</>
)
}
export default CreateCarParts;
The images get sent to the storage, and the doc is created successfully, however the imageUrls array is empty. I can't figure out why it's not working, I assume I'm using the useState() incorrectly.
imageUrls won't be updated in the same render cycle, so calling await addDoc(postsCollectionRef, {imageUrls}); right after await uploadImages(); won't work
I would store urls in a local array instead
function CreateCarParts({isAuth}) {
const [images, setImages] = useState(null);
let navigate = useNavigate();
const postsCollectionRef = collection(db, "CarParts")
useEffect(() => {
if (!localStorage.getItem('isAuth')){
navigate("/login");
};
}, []);
const uploadImages = async () => {
if(images === null) return;
let uniqueFolderName = new Date().getTime().toString();
const imageList = Array.from(images);
const urls = [];
for(let i = 0; i < imageList.length; i++) {
const image = imageList[i];
const imageLinkName = `carParts/${uniqueFolderName}/${image.name}`;
const imageRef = ref(storage, imageLinkName);
const snapshot = await uploadBytes(imageRef, image);
const url = await getDownloadURL(snapshot.ref);
urls.push(url);
}
alert("Images Uploaded");
return urls;
};
const createPost = async () => {
const imageUrls = await uploadImages();
await addDoc(postsCollectionRef, {imageUrls});
navigate('/carparts');
};
return (
<>
<Form.Control onChange={(event) => {setImages(event.target.files);uploadImages();}} type="file" multiple />
<Button onClick={createPost}> Submit </Button>
</>
)
}
export default CreateCarParts;

Firestore getDoc() not saving on useState() hook in reactjs

I'm trying to get a doc value from firebase using the getDoc() method. It's working. But can't save on useState() hook in ReactJS
Here is my code:
const [post, setPost] = React.useState(null);
React.useEffect(() => {
const fetchPost = async () => {
const docSnap = await getDoc(doc(db, "posts", id));
setPost(docSnap.data())
}
return () => fetchPost();
}, []);

How do I make useState hook work with my function?

I am trying to execute a function to update a setState but it as well needs other state to load first.
const [chatsIds, setChatsIds] = useState([]);
const [chats, setChats] = useState([]);
useEffect(() => {
getChatsIds();
}, []);
useEffect(() => {
getChats();
}, [chats]);
the "getChats" needs the value from "chatsIds" but when the screen is loaded the value isn't , only when i reload the app again it gets the value.
Here are the functions :
const getChatsIds = async () => {
const ids = await getDoc(userRef, "chats");
setChatsIds(ids);
}
const getChats = async () => {
const chatsArr = [];
chatsIds.forEach(async (id) => {
const chat = await getDoc(doc(db, "Chats", id));
chatsArr.push(chat);
console.log(chatsArr);
});
setChats(chatsArr);
}
I've tried with the useEffect and useLayoutEffect hooks, with promises and async functions, but i haven't found what i'm doing wrong :(
The problem is in your useEffect hook dependency. It should depends on chatsIds not chats.
useEffect(() => {
getChats();
}, [chatsIds]);
Which mean fetching chatsIds should depend on first mount and fetching chats should depend on if chatsIds is chnaged.
You simply change the useEffect hook to like below.
useEffect(() => {
getChatsIds();
}, [chatsIds]);
I Think getChat() is depend on chatIds...
so you use useEffect with chatIds on dependency
const [chatsIds, setChatsIds] = useState([]);
const [chats, setChats] = useState([]);
useEffect(() => {
getChatsIds();
}, []);
useEffect(() => {
getChats(chatsIds);
}, [chatsIds]);
const getChatsIds = async () => {
const ids = await getDoc(userRef, "chats");
setChatsIds(ids);
}
const getChats = async (chatsIds) => {
const chatsArr = [];
chatsIds.forEach(async (id) => {
const chat = await getDoc(doc(db, "Chats", id));
chatsArr.push(chat);
console.log(chatsArr);
});
setChats(chatsArr);
}

How can I query data from nested collections in Expo from Firestore

[This is the document structure][1] I need to get data from nested collection (journal/userID/entries) in Firestore into a Flatlist (React Native/Expo). Here is my code snippet:
function TextEntry() {
const ref =
firebase
.firestore()
.collection('journal')
.doc(currentUserUID)
.collection('entry')
useEffect(() => {
return ref.onSnapshot(querySnapshot => {
const list = [];
console.log(currentUserUID)
querySnapshot.forEach(doc => {
const { title } = doc.data()
list.push({
id: doc.id,
title,
});
});
setTodos(list);
if (loading) {
setLoading(false);
}
});
}, []);
const [todo, setTodo] = useState('');
const [loading, setLoading] = useState(true);
const [todos, setTodos] = useState([]);
let currentUserUID = firebase.auth().currentUser.uid;
async function addTodo() {
await ref.add({
title: todo,
});
setTodo('');
}

Fetching and displaying images from firebase on React.js app

so I am trying to fetch images from Firebase storage and then display them in a React component. Frankly, I'm new to React/Javascript and I'm having a hard time understanding the asynchronous nature of React/JS and I'm running into the problem where my images are fetched but the component is not re-rendered upon completion of the fetch...
This is the code on my main page, in the useEffect function, I am trying to fetch the images and then store their download urls in an array and then set that array in state (upon page load i.e. only once). Since these are promises, its not happening synchronously and when I first load the page it displays no images. If I, however, click on a different component on the page, it re-renders my Pictures component and the images show up(??), so I know the fetch has worked.
let storageRef = firebase.storage().ref()
let calendarRef = React.createRef()
const position = props.location.state.name.indexOf("#")
const username = props.location.state.name.substring(0, position);
const [simpleDate, setSimpleDate] = useState(null)
const [selectedDate, setSelectedDate] = useState('')
const [showModal, setShowModal] = useState(false)
const [showCancelModal, setShowCancelModal] = useState(false)
const [expectedPeople, setExpectedPeople] = useState(null)
const [events, setEvents] = useState([])
const [helpModal, showHelpModal] = useState(false)
const [pictureURLs, setPictureURLs] = useState([])
useEffect(() => {
//load pictures
const fetchImages = async () => {
let urls = []
storageRef.child('cabinPictures').listAll().then((result) => {
result.items.forEach((imageRef) => {
imageRef.getDownloadURL().then(url => {
urls.push(url)
})
})
})
return urls;
}
fetchImages().then(urls => {
setPictureURLs(urls);
console.log("inside .then() " + pictureURLs)
})
//fetch reservations
firebase
.firestore()
.collection('reservations')
.onSnapshot(serverUpdate => {
const reservations = serverUpdate.docs.map(_doc => {
const data = _doc.data();
data['id'] = _doc.id;
return data;
});
let fetchedEvents = reservations.map(reservation => {
let date = reservation.reservationDate.toDate()
const month = ("0" + (date.getUTCMonth() + 1))
let dateString = date.getUTCFullYear() + "-" + ("0" + (date.getUTCMonth()+1)).slice(-2) + "-" + ("0" + date.getUTCDate()).slice(-2)
return {title: reservation.username + " - " + reservation.numPeople + " total", date: dateString, id: reservation.id, totalPeople: reservation.numPeople, month: month}
})
console.log(fetchedEvents)
setEvents(fetchedEvents)
});
}, [])
My Pictures component in the main page where the useEffect (above) is run. I pass the urls from state as a prop:
<div className="pictures-div-container">
<Pictures pictureURLs={pictureURLs}>
</Pictures>
</div>
The code for my Picture component:
import React, { useState, useEffect } from 'react'
import styles from "./styles.css"
const firebase = require('firebase');
const Pictures = (props) => {
const [uploadImage, setUploadImage] = useState(null)
const [progressValue, setProgressValue] = useState(0)
let storageRef = firebase.storage().ref()
let { pictureURLs } = props
const handleUpload = () => {
setProgressValue(0)
const uploadTask = storageRef.child(`cabinPictures/${uploadImage.name}`).put(uploadImage)
uploadTask.on('state_changed',
(snapshot) => {
//progress function
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes ) * 100)
setProgressValue(progress)
},
(error) => {
//error function
console.log(error)
},
() => {
//complete function
storageRef.child('cabinPictures').child(uploadImage.name).getDownloadURL().then(url => {
console.log(url)
} )
});
}
const handleFileSelect = (e) => {
if (e.target.files[0]) {
setUploadImage(e.target.files[0])
}
}
return (
<div className="pictures-container">
<h2>Upload a Picture!</h2>
<button className="upload-button" onClick={() => handleUpload()}>Upload</button>
<input type="file" onChange={(e) => handleFileSelect(e)}></input>
<progress value={progressValue} max="100"></progress>
<div className="pictures">
{
pictureURLs.map((url, index) => {
return <img className="picture" key={index} src={url}></img>
})
}
</div>
</div>
)
}
export default Pictures
So, can anyone help me understand why the Pictures component is not re-rendering automatically when the state is set after fetching the picture urls from firebase? I thought that when a prop changes in a component, the whole component is re-rendered?
EDIT:
So this is what I changed in my main page's useEffect function as per the answer's suggestions, and it works!
//fetch and load pictures
const fetchImages = async () => {
let result = await storageRef.child('cabinPictures').listAll();
let urlPromises = result.items.map(imageRef => imageRef.getDownloadURL())
return Promise.all(urlPromises)
}
const loadImages = async () => {
const urls = await fetchImages()
setPictureURLs(urls)
}
loadImages()
You have to let all the nested promises resolve before you return urls
I am not very current on firebase API so am not sure if result.items is an actual array or an object that has a foreach method. Following should work if it is a js array
Try something like:
//load pictures
const fetchImages = async() => {
let result = await storageRef.child('cabinPictures').listAll();
/// map() array of the imageRef.getDownloadURL() promises
let urlPromises = result.items.map(imageRef => imageRef.getDownloadURL());
// return all resolved promises
return Promise.all(urlPromises);
}
const urls = await fetchImages()
setPictureURLs(urls);
console.log("inside .then() ", urls)

Categories