Cant write Data to Firestore using React Native and Gifted Chats - javascript

I am trying to implment a Chat functionality in my React Native App.
The Problem i am facing is, that if i send a message it doesnt get saved in the Firestore Database. But if i remove the useLayoutEffect Function, the Messages get Saved in Firestore.
Ive got the code from this Page:
Here is my Code:
import React, {useCallback, useLayoutEffect, useState} from 'react';
import {addDoc, collection, query, orderBy, onSnapshot} from 'firebase/firestore';
import {db} from '../../../firebase';
import {GiftedChat} from 'react-native-gifted-chat';
export const EventChatScreen = () => {
const [messages, setMessages] = useState([])
useLayoutEffect(() => {
const collectionRef = collection(db, 'chats');
const q = query(collectionRef, orderBy('createdAt', 'desc'));
return onSnapshot(q, QuerySnapshot =>
{
setMessages(
QuerySnapshot.docs.map(doc => ({ // these key:value pairs are in a required format for GiftedChat
_id: doc.data()._id,
createdAt: doc.data().createdAt.toDate(),
text: doc.data().text,
user: doc.data().user
}))
);
});
}, []);
const onSend = useCallback((messages = []) => {
setMessages(previousMessages =>
GiftedChat.append(previousMessages, messages)
);
const { _id, createdAt, text, user } = messages[0];
addDoc(collection(db, 'chats'), {
_id,
createdAt,
text,
user
});
}, []);
return (
<GiftedChat
messages={messages}
showAvatarForEveryMessage={true}
onSend={messages => onSend(messages)}
user={{
_id: 1,
avatar: 'https://i.pravatar.cc/300'
}}
/>
);
}
export default EventChatScreen
As i mentioned before. As soon as i remove the useLayoutEffect it saves the Data to Firebase.
Ive also tried to get the Return Value of the Add Doc. But as soon as the useLayoutEffect function is added, it doesnt return a Value.
EDIT: Now it works while i am in the Debug Mode.

Related

Waiting for Firebase RTDB to update state

I am creating a data context for my react native app. There is an array of property objects in a firebase node and the aim is to pull this data into the app on load and provide that data throughout the app. I have a loader that shows if the loading state is true and should only be false if the data has been successfully pulled from firebase.
The issue I am having is the Loader ends before all the data is called and then the properties are not shown on the home page of the app until i refresh the app again.
Below is the code for the app:
import { onValue, ref } from 'firebase/database'
import React, { useEffect, useState } from 'react'
import { projectDatabase } from '../../config'
import useAuth from '../hooks/useAuth'
export const DataContext = React.createContext()
const DataLayer = ({ children }) => {
const { userCredentials: user } = useAuth() //get user data
const [company, setCompany] = useState('');
const [loading, setLoading] = useState(true);
const [properties, setProperties] = useState([]);
useEffect(() => {
const userRef = ref(projectDatabase, 'users/' + user.uid + '/company');
onValue(userRef, (snapshot) => {
setCompany(snapshot.val());
const propertiesRef = ref(projectDatabase, 'providers/' + snapshot.val() + '/properties');
onValue(propertiesRef, (propertiesSnapshot) => {
// console.log(propertiesSnapshot.val())
setProperties(propertiesSnapshot.val());
setLoading(false);
});
});
}, [])
return (
<DataContext.Provider
value={{
properties:properties,
company: company,
loading: loading,
userEnquiries:userEnquiries,
chatId:chatId,
acceptedBookings:acceptedBookings
}}
>
{children}
</DataContext.Provider>
)
}
export default DataLayer
Is there a way to make the call to the firebase RTDB complete BEFORE the loading is resolved as false or do i just do a useEffect call to every page the data is needed? For context the array has 200 properties.
I would really appreciate any help I can get. I love firebase and would really want this to work.

firestore where() filter collection by field for context provider next.js

i have a problem as follows:
i'm retrieving my firestore documents via useEffect function and it works just fine.
Right now I'm retrieving all "profiles" documents (3 at this point). But now i want to retrieve only the documents, which field "workerProfile" is true. I have 2 docs with false and 1 with true, so it should only show me an array with 1 doc.
When doing so in firebase console manually, it is working.
below is my useEffect function with the ,where('workerProfile', '==', true) which is not working, but also not giving any errors.
useEffect(() => {
const getProfiles = async () => {
const querySnapshot = await getDocs(collection(fireDb, "profiles"),where('workerProfile', '==', true))
console.log(querySnapshot, "CHECK OUT")
setProfiles(querySnapshot.docs.map(doc => {
return {
id: doc.id,
data: {
userProfileUrl: doc.data().userProfileUrl,
displayName: doc.data().displayName,
likesCount: doc.data().likesCount,
bio: doc.data().bio
}
}
}))
}
getProfiles()
}, [])
but my console.log(querySnapshot, "CHECK OUT") still shows all 3 profiles, instead of only 1.
I already tried to find some hints from the firebase documentation, but i still have no idea, why it is not working. Maybe I'm missing something here?
I would really appreciate if someone could help me here, because this is starting to annoy me really bad.
I'm working with
firebase 9.14.0
firebase-admin ^11.2.1
firebase-functions ^4.0.2
next 13.0.2
react: 18.2.0
Below is my complete context-file for further information:
import { createContext, useEffect, useState, useContext } from "react"
import { collection, getDocs, getDoc, doc, where, query, setDoc } from "firebase/firestore"
import { fireDb } from "../../firebaseClient"
const FantsyContext = createContext()
const FantsyProvider = ({ children }) => {
const [users, setUsers] = useState([])
const [currentLoggedUser, setCurrentUser] = useState([])
const [profiles, setProfiles] = useState([])
// GET USERS
useEffect(() => {
const getUsers = async () => {
const querySnapshot = await getDocs(collection(fireDb, "users"))
setUsers(querySnapshot.docs.map(doc => {
return {
id: doc.id,
data: {
...doc.data()
}
}
}))
}
getUsers()
}, [])
// GET PROFILES
useEffect(() => {
const getProfiles = async () => {
const querySnapshot = await getDocs(collection(fireDb, "profiles"),where('workerProfile', '==', true))
console.log(querySnapshot, "CHECK OUT")
setProfiles(querySnapshot.docs.map(doc => {
return {
id: doc.id,
data: {
userProfileUrl: doc.data().userProfileUrl,
displayName: doc.data().displayName,
likesCount: doc.data().likesCount,
bio: doc.data().bio
}
}
}))
}
getProfiles()
}, [])
return (
<FantsyContext.Provider
value={{ profiles, users }}
>{children}</FantsyContext.Provider>
)
}
export { FantsyContext, FantsyProvider }
Try
await getDocs(query(collection(fireDb, "profiles"), where('workerProfile', '==', true)))
I think you just used getDocs(collection(<ref>, <collection name>)) and so getDocs() function ignored the rest args(where() query).
So, use query() function inside getDocs().

Why is firebase's Realtime Database not loading data when page refreshes

I am using Firebase Realtime Database for a site I am developing with React. In a useEffect method, I am using Firebase's get method to receive all the data from the database and it works when I switch from the home page back to the page I am displaying the data on but it doesn't work when I refresh my page. I have tried using an async await function, console.logging everything I could think of, and re-writing the entire code.
This is my useEffect method that fetches an input that was previously saved to the database. If I switch from the 'Journal' Router page to Home page and back, it loads correctly but it doesn't load correctly if I refresh the page. When I refresh, it console.logs 'No Data' but I know the data exists because when I switch between router pages it does load.
useEffect(() => {
const dbRef = ref(getDatabase())
//Fetches dreams from firebase's database
get(child(dbRef, `/${user.uid}/dreams`)).then(snapshot => {
if (snapshot.exists()){
const dreams = snapshot.val()
Object.values(dreams).forEach(dream => {
setUserDreams(prev => [...prev, dream])
})
} else {
console.log('No Data')
}
}).catch(err => {
console.error(err);
})
...
}, [])
The JSON structure of the database is basically this
"USER_ID" : {
"dreams" : [{"RANDOM_UUID" : {...}}],
"tags" : [{"RANDOM_UUID" : {...}}]
}
The user ID is the uid that firebase generates in their user authentication feature and it doesn't change and the random uuid is a random string generated from the firebase uuidv4 method.
This is how the user variable is populated:
import {createContext, useContext, useEffect, useState} from 'react'
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
updateProfile,
onAuthStateChanged
} from 'firebase/auth';
import { auth } from '../firebase-config';
const UserContext = createContext();
export const AuthContextProvider = ({children}) => {
const [user, setUser] = useState({})
const createUser = (email, password) => {
return createUserWithEmailAndPassword(auth, email, password);
}
const updateUsername = (username) => {
return updateProfile(auth.currentUser, {
displayName: username
})
}
const signIn = (email, password) => {
return signInWithEmailAndPassword(auth, email, password);
}
const logout = () => {
return signOut(auth);
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
console.log(currentUser)
setUser(currentUser)
})
return () => {
unsubscribe()
}
}, [])
return (
<UserContext.Provider value={{createUser, user, logout, signIn, updateUsername}}>
{children}
</UserContext.Provider>
)
}
export const UserAuth = () => {
return useContext(UserContext)
}
Sorry if this is a bit weird but I figured out the issue. After logging the user variable in my journal file, I learned that it isn't populated until after that useEffect is ran so I just put user as the dependency variable in my useEffect hook so it waits until it is populated to run that hook.
useEffect(() => {
const dbRef = ref(getDatabase())
//Fetches dreams from firebase's database
get(child(dbRef, `/${user.uid}/dreams`)).then(snapshot => {
if (snapshot.exists()){
const dreams = snapshot.val()
Object.values(dreams).forEach(dream => {
setUserDreams(prev => [...prev, dream])
})
} else {
console.log('No Data')
}
}).catch(err => {
console.error(err);
})
...
}, [user])
This is what worked, the only thing changed was the dependency array. Meaning, the user variable was populated after the useEffect hook ran which is what made me have issues. Thanks for the commenter that helped me out!

Custom Hook Returning Empty Array From Firestore

I'm a newly self-taught coder. Hopefully I'm expressing my situation adequately.
I'm trying to retrieve some data from Cloud Firestore. I've made a custom hook that should useState and setDocuments to the be the retrieved "guide". The query uses useParams to get the id to match to the specific guide I'm trying to get. I think the issue is in the querySnapshot. setDocuments doesn't seem to be working. When I console log "documents" it's an empty array.
Any leads?
import { useParams } from 'react-router-dom'
import { collection, query, where, getDocs } from "firebase/firestore";
import { db } from "../firebase/config"
import { useEffect } from 'react';
export const useGuide = (c) => {
const [documents, setDocuments] = useState([])
const { id } = useParams()
useEffect(() => {
const ref = collection(db, c)
const q = query(ref, where("id", "==", `${id}`))
getDocs(q).then(querySnapshot => {
querySnapshot.forEach((doc) => {
setDocuments(doc.data())
});
});
}, [])
console.log(documents)
return { documents }
}
Here is where I try to use the hook useGuide to set the state which would be passed to a component.
import SingleGuide from '../../components/SingleGuide/SingleGuide'
import { useGuide } from '../../hooks/useGuide'
function Guide() {
const { documents: guides } = useGuide('guides')
console.log(guides)
return (
<div>
{guides && <SingleGuide guide={guides}/>}
</div>
)
}
export default Guide
There are a few issues with your code including setting an array of docs equal to a single doc in the query results. Try something like the following.
import { useParams } from "react-router-dom";
import { collection, query, where, getDocs } from "firebase/firestore";
import { db } from "../firebase/config";
import { useState, useEffect } from "react";
export const useGuide = (c) => {
const [documents, setDocuments] = useState([]);
const { id } = useParams();
useEffect(() => {
// create array to hold all the individual docs
const docs = [];
const ref = collection(db, c);
const q = query(ref, where("id", "==", `${id}`));
getDocs(q).then((querySnapshot) => {
querySnapshot.forEach((doc) => {
docs.push(doc.data());
});
setDocuments(docs);
});
}, [c]); // could consider passing `id` to the hook instead of useParams and adding that as a dependency to update data automatically
console.log(documents);
return { documents };
};

How to update react context after pulling data from firebase

Hey everyone pretty new to React hooks. I am simply trying to set some reviews that I retrieve from Firebase but cant seem to get it working. I tried a few solutions and I am struggling to get it working any help would be appreciated.
import React, {useContext, useEffect, useState} from 'react';
import firebase from "firebase";
import ReviewsContext from "./review-context";
const Reviews = () => {
const db = firebase.firestore();
let reviews = useContext(ReviewsContext);
let [reviewsLoaded, setReviewsLoaded] = useState(false);
function getReviews(){
db.collection('reviews')
.get()
.then((snapshot) => {
let dataArray = [];
snapshot.docs.forEach(doc => {
dataArray.push(doc.data());
});
reviews = dataArray;
setReviewsLoaded(true);
console.log('reviews', reviews); // logs the correct amount of reviews
})
}
function renderReviews() {
console.log('renderReviews reviewsLoaded', reviewsLoaded); // is true
console.log('renderReviews reviews length', reviews.length); // is 0
if(reviewsLoaded) {
reviews.map((data) => {
return (
<li key={data.name}>
<h3>{data.name}</h3>
<p>{data.position}</p>
</li>
)
});
}
else {
return false
}
}
useEffect(() => {
getReviews(); // this seems to fire before renderReviews
}, []);
return (
<div>
<ul>
{renderReviews()}
</ul>
</div>
)
};
export default Reviews;
In this case, the context should be stateful. The way you're doing it currently won't work since context on render will always revert to reviews being empty. Your Provider component that gives that ReviewContext should be patterned like below.
import React, { createContext, useState } from "react"
const ReviewContext = createContext()
const ReviewProvider = ({children}) => {
const [reviews, setReviews] = useState([])
return (
<ReviewContext.Provider value={{
reviews: reviews,
setReviews: reviews => setReviews(reviews),
}}>
{children}
</ReviewContext.Provider>
)
}
export default ReviewProvider
export { ReviewContext }
Now, you may do const { reviews, setReviews } = useContext(ReviewContext); Just call setReviews whenever you want to update reviews in the context.
It's actually stated in the docs as well as I searched it. https://reactjs.org/docs/context.html#dynamic-context

Categories