Delete image Doc from firebase firestore - javascript

I am trying to delete a document from firebase firestore collection
how can I do that , I tried few things but no success
here is my code
here is the Firestore hook
const useFirestore = (somecollection) => {
const [docs, setDocs] = useState([]);
useEffect (() => {
// new collection reference
const newcoll = collection(projectFirestore, somecollection);
const q = query(newcoll, orderBy('createdAt', 'desc'));
const unsub = onSnapshot(q, (snapshot) => {
let documents = [];
snapshot.forEach(doc => {
documents.push({...doc.data(), id: doc.id})
})
setDocs(documents);
});
return () => unsub;
},[somecollection]);
return { docs };
}
the imagegrid where all the images are shown (only the js without the jsx return)
const { docs } = useFirestore('images');
const handleDelete = (e) => {
docs.forEach(doc => {
deleteDoc(doc);
})
now , on the imagegrid jsx I have all the uploaded images that store the document in them.
so I added a button to every image and I want that when I click the button its fires a handleDelete() that delete this specific image document from the firestore

If you are trying to have a button that deletes a single document, you likely do NOT want to be looping over all of the docs and calling deleteDoc().
Instead, your UI would likely display each image and include its ID for each button. Something like:
const deleteOneImageDoc = async (docId) => {
try {
let docRef = doc(myFirestore, `images/${docId}`);
await deleteDoc(docRef);
} catch (ex) {
console.error(`Delete FAILED: ${ex.message}`);
throw ex;
}
};
return <div>
docs.map((imgDoc) => {
return (
<div key={imgDoc.id}>
<img src={imgDoc.url}/>
<button onClick={() => deleteOneImageDoc(imgDoc.id)}>
delete {imgDoc.id}
</button>
</div>);
})
</div>;

Related

Reactjs problem rendering binary image retrieved from mongodb

Hello so I have images in a mongodb database and I'm trying to render them on the client side however It's not working. I convert the buffer unit8 data into base64 so I can render it it seemd to work and been stored in the images state but images are not accessible by associative array.
useEffect( () => {
const getAllDoctors = async () => {
const result = await api.get('doctor/all')
const myImages = []
setDoctors(result.data)
await result.data.forEach(async doctor => {
myImages[doctor._id] = await base64_arraybuffer(doctor.photo.data.data)
})
setImages(myImages)
setLoading(false)
}
getAllDoctors()
}, [])
as for the render
return (
<div>
{
images.map((image, key) => {
console.log(doctors)
return (
<div key={key}>
<img alt={'image'} src={`data:image/png; base64, ${image}`}/>
<div>{`Doctor + ${images}`}</div>
</div>
)
})
}
</div>
);
converter (not mine):
const base64_arraybuffer = async (data) => {
const base64url = await new Promise((r) => {
const reader = new FileReader()
reader.onload = () => r(reader.result)
reader.readAsDataURL(new Blob([data]))
})
return base64url.split(",", 2)[1]
}
Two things
The way your array assignment is doesn't make sense. If _id is a ID string, then you are using a string to key an array, the data assigned at that key won't be included in loops. Use Array.prototype.push to add the item to the next available index.
Array.prototype.forEach and async / await don't play nice together due to the nature of callbacks. Try using a traditional loop.
useEffect(() => {
const getAllDoctors = async () => {
const result = await api.get("doctor/all");
const myImages = [];
setDoctors(result.data);
for (const doctor of result.data) {
const image = await base64_arraybuffer(doctor.photo.data.data);
myImages.push(image)
}
setImages(myImages);
setLoading(false);
};
getAllDoctors();
}, []);

.map is returning only the last element of array React

I'm creating a blog type app with cloud firestore just as a project to learn some stuff. However I'm having an issue where only the last element of my state array, 'blogs', is being returned inside of the .map method.
Any help with this is appreciated, thanks!
const [blogs, setBlog] = useState([]);
const fetchBlogs = async () => {
const response = await getDocs(collection(db, "blog posts"));
let postArray = [];
response.docs.forEach((blogpost) => {
postArray.push(blogpost.data());
});
console.log(postArray)
postArray.forEach(post => {
setBlog([...blogs, post]);
});
}
useEffect(() => {
fetchBlogs();
}, []);
return (
<ul className="posts-container">
{ blogs.map((blog) => {
console.log(blog); //this outputs each of the individual posts, but only the last is displayed as an <li>
return ( <li key={blog.title}>{blog.title}</li> )
})}
</ul>
)
You probably want to put the response in your state without doing the forEach()
setBlog(postArray);
Or even (depending on data structure)
const [blogs, setBlog] = useState([]);
const fetchBlogs = async () => {
const response = await getDocs(collection(db, "blog posts"));
setBlog(response.data); // set to whatever you need
}
useEffect(() => {
fetchBlogs();
}, []);
return (
<ul className="posts-container">
{ blogs.map((blog) => {
return <li>( <li key={blog.title}>{blog.title}</li> )
}
)}
</ul>
);
Instead of this:
postArray.forEach(post => {
setBlog([...blogs, post]);
});
Try doing this:
setBlog(postArray);
I am not sure as to what exactly happens, but this seems like the most suspicious piece in your code.
You are overriding the blog post twice, try it this way
const fetchBlogs = async () => {
const response = await getDocs(collection(db, "blog posts"));
setBlog([...blogs, ...response.docs.map(blogpost => blogpost.data())])
}
useEffect(() => fetchBlogs(), []);

Array.map() doesn't render anything in React

I'm trying to make a list in my react app. I have retrieved data from my database, and pushed it into a list. I have doublechecked that the data shows up correctly in the console, and it does, but array.map() returns nothing. I think the problem might be that array.map() runs two times. I don't know why it runs two times.
function Dashboard() {
const user = firebase.auth().currentUser;
const [teams, setTeams] = useState([])
const history = useHistory();
useEffect(() => {
getTeams()
if (user) {
} else {
history.push("/")
}
}, [])
function Welcome() {
if (user) {
return <h1>Welcome, {user.displayName}</h1>
} else {
}
}
const getTeams = () => {
firebase.firestore().collectionGroup('members').where('user', '==', user.uid).get().then((snapshot) => {
const docList = []
snapshot.forEach((doc) => {
docList.push({
teamId: doc.data().teamId,
})
})
const teamslist = []
docList.forEach((data) => {
firebase.firestore().collection('teams').doc(data.teamId).get().then((doc) => {
teamslist.push({
name: doc.data().name,
teamId: doc.id,
})
})
})
setTeams(teamslist)
})
}
const openTeam = (data) => {
console.log(data.teamId)
}
return (
<div>
<Welcome />
<div>
<ul>
{console.log(teams)}
{teams.map((data) => {
return (
<li onClick={() => openTeam(data)} key={data.teamId}>
<h1>{data.name}</h1>
<p>{data.teamId}</p>
</li>
)
})}
</ul>
</div>
</div>
)
}
export default Dashboard
The getTeams function has a bug where it isn't waiting for the firebase.firestore().collection('teams').doc(data.teamId).get().then promises to finish before calling setTeams, so it is called with an empty array, causing React to trigger a render with the empty array.
As the promises for fetching each team resolve they will be pushed to the same array reference, but this won't trigger a rerender in React since you're not calling setTeams again when the array changes.
Try this code, which won't call setTeams until each team promise generated from docList has been resolved.
const getTeams = () => {
firebase.firestore().collectionGroup('members').where('user', '==', user.uid).get().then((snapshot) => {
const docList = []
snapshot.forEach((doc) => {
docList.push({
teamId: doc.data().teamId,
})
})
const teamslist = [];
Promise.all(docList.map((data) => {
return firebase
.firestore()
.collection('teams')
.doc(data.teamId)
.get()
.then((doc) => {
teamslist.push({
name: doc.data().name,
teamId: doc.id,
})
})
}))
.then(() => setTeams(teamslist));
})
}
A smaller edit would be to call setTeams after each separate team promise resolves, which will trigger a React render each time a new team is resolved:
.then((doc) => {
teamslist.push({
name: doc.data().name,
teamId: doc.id,
});
// create a new array, since using the same array
// reference won't cause react to rerender
setTeams([...teamslist]);
})
Many thanks to #martinstark who provided you an answer while I was unavailable.
However, there are some more things that need to be covered.
User State
In your current component, you pull the current user from Firebase Authentication, but don't handle the state changes of that user - signing in, signing out, switching user. If a user is signed in and they were to navigate directly to your dashboard, firebase.auth().currentUser could be momentarily null while it resolves the user's login state, which would incorrectly send them off to your login page.
This can be added using:
const [user, setUser] = useState(() => firebase.auth().currentUser || undefined);
const userLoading = user === undefined;
useEffect(() => firebase.auth().onAuthStateChanged(setUser), []);
Next, in your first useEffect call, you call getTeams() whether the user is signed in or not - but it should depend on the current user.
useEffect(() => {
if (userLoading) {
return; // do nothing (yet)
} else if (user === null) {
history.push("/");
return;
}
getTeams()
.catch(setError);
}, [user]);
// This getTeams() is a () => Promise<void>
const getTeams = async () => {
const membersQuerySnapshot = await firebase.firestore()
.collectionGroup('members')
.where('user', '==', user.uid)
.get();
const docList = []
membersQuerySnapshot.forEach((doc) => {
docList.push({
teamId: doc.get("teamId"), // better perfomance than `doc.data().teamId`
});
});
const teamDataList = [];
await Promise.all(docList.map((data) => {
return firebase.firestore()
.collection('teams')
.doc(data.teamId)
.get()
.then(doc => teamDataList.push({
name: doc.get("name"),
teamId: doc.id
}));
}));
setTeams(teamDataList);
}
Optimizing getTeams() - Network Calls
The getTeams function in your question calls setTeams with the array [], which will be empty at the time of calling it as covered in #martinstark's answer. The "get team data" operations are asyncronous and you aren't waiting for them to resolve before updating your state and triggering a new render. While you are pushing data to them after the component has rendered, modifying the array won't trigger a new render.
While you could fetch the data for each team using db.collection("teams").doc(teamId).get(), each of these is requests is a network call, and you can only make a limited number of these in parallel. So instead of fetching 1 team per network call, you could fetch up to 10 teams per network call instead using the in operator and FieldPath.documentId().
Assuming the collectionGroup("members") targets the collections of documents at /teams/{aTeamId}/members which contain (at least):
"/teams/{aTeamId}/members/{memberUserId}": {
teamId: aTeamId,
user: memberUserId, // if storing an ID here, call it "uid" or "userId" instead
/* ... */
}
// this utility function lives outside of your component near the top/bottom of the file
function chunkArr(arr, n) {
if (n <= 0) throw new Error("n must be greater than 0");
return Array
.from({length: Math.ceil(arr.length/n)})
.map((_, i) => arr.slice(n*i, n*(i+1)))
}
// This getTeams() is a () => Promise<void>
const getTeams = async () => {
const membersQuerySnapshot = await firebase.firestore()
.collectionGroup('members')
.where('user', '==', user.uid)
.get();
const teamIDList = []
membersQuerySnapshot.forEach((doc) => {
teamIDList.push(doc.get("teamId")); // better perfomance than `doc.data().teamId`
})
const chunkedTeamIDList = chunkArr(teamIDList, 10) // split into batches of 10
const teamsColRef = firebase.firestore().collection('teams');
const documentId = firebase.firestore.FieldPath.documentId(); // used with where() to target the document's ID
const foundTeamDocList = await Promise
.all(chunkedTeamIDList.map((chunkOfTeamIDs) => {
// fetch each batch of IDs
return teamsColRef
.where(documentId, 'in', chunkOfTeamIDs)
.get();
}))
.then((arrayOfQuerySnapshots) => {
// flatten results into a single array
const allDocsList = [];
arrayOfQuerySnapshots.forEach(qs => allDocsList.push(...qs.docs));
return allDocsList;
});
const teamDataList = foundTeamDocList
.map((doc) => ({ name: doc.get("name"), teamId: doc.id }));
// sort by name, then by ID
teamDataList.sort((aTeam, bTeam) =>
aTeam.name.localeCompare(bTeam.name) || aTeam.teamId.localeCompare(bTeam.teamId)
)
// update state & trigger render
setTeams(teamDataList);
}
You can also make use of this utility function to simplify & optimize the code a bit. Which gives:
// This getTeams() is a () => Promise<void>
const getTeams = async () => {
const membersQuerySnapshot = await firebase.firestore()
.collectionGroup('members')
.where('user', '==', user.uid)
.get();
const teamIDList = []
membersQuerySnapshot.forEach((doc) => {
teamIDList.push(doc.get("teamId")); // better perfomance than `doc.data().teamId`
})
const teamsColRef = firebase.firestore().collection('teams');
const teamDataList = [];
await fetchDocumentsWithId(
teamsColRef,
teamIDList,
(doc) => teamDataList.push({ name: doc.get("name"), teamId: doc.id })
);
// sort by name, then by ID
teamDataList.sort((aTeam, bTeam) =>
aTeam.name.localeCompare(bTeam.name) || aTeam.teamId.localeCompare(bTeam.teamId)
)
// update state & trigger render
setTeams(teamDataList);
}
Optimizing getTeams() - Function Definition
As part of the last optimization, you could pull it out of your component or place it in its own file so that it's not redefined with every render:
// define at top/bottom of the file outside your component
// This getTeams() is a (userId: string) => Promise<{ name: string, teamId: string}[]>
async function getTeams(userId) => {
const membersQuerySnapshot = await firebase.firestore()
.collectionGroup('members')
.where('user', '==', userId)
.get();
const teamIDList = []
membersQuerySnapshot.forEach((doc) => {
teamIDList.push(doc.get("teamId")); // better perfomance than `doc.data().teamId`
})
const teamsColRef = firebase.firestore().collection('teams');
const teamDataList = [];
await fetchDocumentsWithId(
teamsColRef,
teamIDList,
(doc) => teamDataList.push({ name: doc.get("name"), teamId: doc.id })
);
// sort by name, then by ID
teamDataList.sort((aTeam, bTeam) =>
aTeam.name.localeCompare(bTeam.name) || aTeam.teamId.localeCompare(bTeam.teamId)
)
// return the sorted teams
return teamDataList
}
and update how you use it:
useEffect(() => {
if (userLoading) {
return; // do nothing
} else if (user === null) {
history.push("/");
return;
}
getTeams(user.uid)
.then(setTeams)
.catch(setError);
}, [user]);

Assist in getting data from cache

This screen is somewhat working. On the first time you open the screen, only the splash screen shows up. Go back then open the screen again then the data shows up. I need help in getting the data from cache and show it the first time the screen opens.
I need the screen to show splash screen while doing the saving and retrieving data from cache then show the actual data after that on the first time you open the screen.
const [dataList1, setDataList1] = useState([]);
const [cacheData, setCacheData] = useState(null);
// Function to get data from cache
const getDataFromCache = async () => {
const data = await cache.get(cacheKeys.BENEFITS_CACHE_KEY);
setCacheData(data);
}
// Get data from cache
useEffect(() => {
getDataFromCache();
}, [])
// Store data to cache
useEffect(() => {
if (dataList1.length > 1) cache.store(cacheKeys.BENEFITS_CACHE_KEY, dataList1);
}, [dataList1]);
useEffect(() => {
var benefitsArr = [];
var benefitsDetailsArr = [];
const unsubscribe = auth.onAuthStateChanged((authUser) => {
if (authUser) {
db
.collection('members')
.doc(authUser.uid)
.get()
.then(user => {
benefitsArr.push(...user.data().benefits);
db
.collection('benefits')
.get()
.then(snapshots => {
snapshots.forEach(doc => {
if (benefitsArr.includes(doc.id))
{
benefitsDetailsArr.push(doc.data());
}
})
// Save results to a useState constant
setDataList1(dataList1 => ([...dataList1, ...benefitsDetailsArr]));
})
.catch(error => console.log(error))
})
.catch(error => console.log(error))
}
})
return unsubscribe;
}, []);
if (!cacheData) return <SplashScreen />
it is because of this line
if (!cacheData) return <SplashScreen />
You should create a show/hide state then do
if (show) return <SplashScreen />
Then in your function
const getDataFromCache = async () => {
const data = await cache.get(cacheKeys.BENEFITS_CACHE_KEY);
setCacheData(data);
setShow(false) // <-- hide the splash
}

React-native-gifted-chat with cloud firestore pagination

I'm using Firestore to store messages. In order to optimize the mobile application performances, I would like to set a limit(50) in the firestore query.
It works well and implemented the onLoadEarlier React-native-gifted-chat available in the props.
All is working fine.
But, when I send a new message in the chat, after scrolled up to see the earliers messages, only the 50 last messages with the new one, off course, are available.
So, each time I'm adding a message in the Firestore database, the onSnapshot (in the useeffect) is executed and apply the limit query.
Is there a way to avoid this ?
Thanks.
Here my useEffect :
useEffect(() => {
const messagesListener = firestore()
.collection('groups')
.doc(group._id)
.collection('messages')
.orderBy('createdAt', 'desc')
.limit(50)
.onSnapshot(querySnapshot => {
const newMessages = querySnapshot.docs.map(doc => {
const firebaseData = doc.data();
const data = {
_id: doc.id,
text: '',
createdAt: new Date().getTime(),
...firebaseData
};
return data;
});
setMessages(previousMessages => {
return GiftedChat.append(previousMessages, newMessages);
});
});
return () => messagesListener();
}, []);
I am using FlatList in react-native to render chats and I had to paginate the chats list. Since Firestore query cursor is not supported in live listener, I created two list, recentChats & oldChats.
I populate recentChats using live listener query.onSnapshot & oldChats using cursor startAfter. FlatList data is combination of both list and I take care of merging logic.
const MESSAGE_LIMIT = 15;
const ChatWindow = props => {
const { sessionId, postMessage, onSendTemplateButtonPress } = props;
// Firestore cursor is not supported in query.onSnapshot so maintaining two chat list
// oldChats -> chat list via cursor, recentChats -> chat list via live listener
const [oldChats, setOldChats] = useState([]);
const [recentChats, setRecentChats] = useState([]);
// if true, show a loader at the top of chat list
const [moreChatsAvailable, setMoreChatsAvailable] = useState(true);
const [inputMessage, setInputMessage] = useState('');
useEffect(() => {
const query = getGuestChatMessagesQuery(sessionId)
.limit(MESSAGE_LIMIT);
const listener = query.onSnapshot(querySnapshot => {
let chats = [];
querySnapshot.forEach(snapshot => {
chats.push(snapshot.data());
});
// merge recentChats & chats
if (recentChats.length > 0) {
const newRecentChats = [];
for (let i = 0; i < chats.length; i++) {
if (chats[i].sessionId === recentChats[0].sessionId) {
break;
}
newRecentChats.push(chats[i]);
}
setRecentChats([...newRecentChats, ...recentChats]);
} else {
setRecentChats(chats);
if (chats.length < MESSAGE_LIMIT) {
setMoreChatsAvailable(false);
}
}
});
return () => {
// unsubscribe listener
listener();
};
}, []);
const onMessageInputChange = text => {
setInputMessage(text);
};
const onMessageSubmit = () => {
postMessage(inputMessage);
setInputMessage('');
};
const renderFlatListItem = ({ item }) => {
return (<ChatBubble chat={item} />);
};
const onChatListEndReached = () => {
if (!moreChatsAvailable) {
return;
}
let startAfterTime;
if (oldChats.length > 0) {
startAfterTime = oldChats[oldChats.length - 1].time;
} else if (recentChats.length > 0) {
startAfterTime = recentChats[recentChats.length - 1].time;
} else {
setMoreChatsAvailable(false);
return;
}
// query data using cursor
getGuestChatMessagesQuery(sessionId)
.startAfter(startAfterTime)
.limit(MESSAGE_LIMIT)
.get()
.then(querySnapshot => {
let chats = [];
querySnapshot.forEach(snapshot => {
chats.push(snapshot.data());
});
if (chats.length === 0) {
setMoreChatsAvailable(false);
} else {
setOldChats([...oldChats, ...chats]);
}
});
};
return (
<View style={[GenericStyles.fill, GenericStyles.p16]}>
<FlatList
inverted
data={[...recentChats, ...oldChats]}
renderItem={renderFlatListItem}
keyExtractor={item => item.messageId}
onEndReached={onChatListEndReached}
onEndReachedThreshold={0.2}
ListFooterComponent={moreChatsAvailable ? <ActivityIndicator /> : null}
/>
{
Singleton.isStaff ?
null:
<ChatInput
onMessageInputChange={onMessageInputChange}
onMessageSubmit={onMessageSubmit}
inputMessage={inputMessage}
style={GenericStyles.selfEnd}
onSendTemplateButtonPress={onSendTemplateButtonPress}
/>
}
</View>
);
};
Your query is OK for the first time, for consequent queries you must use the ::startAt or ::startAfter methods.
You can find more information in the official documentation.
https://firebase.google.com/docs/firestore/query-data/query-cursors

Categories

Resources