Cannot read properties of undefined (reading 'indexOf') in firebase v9 {modular} - javascript

I changed my code from firebase v8 to v9, I am able to write the data in db but couldn't get and map it.
My data is stored in db like this : users >> user.uid >> orders >> paymentIntent.id,
each doc saved has amount, basket data, created
Please check mentioned screenshot.
Previous code:
useEffect(() => {
if (user) {
db.collection("users")
.doc(user?.uid)
.collection("orders")
.orderBy("created", "desc")
.onSnapshot((snapshot) => {
// console.log(snapshot);
setOrders(
snapshot.docs.map((doc, i) => ({
key: { i },
id: doc.id,
data: doc.data(),
}))
);
});
} else {
setOrders([]);
}
}, [user]);
My data is stored in db like this : users >> user.uid >> orders >> paymentIntent.id,
each doc saved has amount, basket data, created
Please check mentioned screenshot.
This is firestore inserted data
firestore added data in db, screenshot
This is my new code
import { collection, doc, getDoc, getDocs, onSnapshot, orderBy, query } from 'firebase/firestore';
import React, { useEffect, useState } from 'react'
import { db } from './firebase';
import './Orders.css'
import { useStateValue } from './StateProvider';
import Order from './Order';
function Orders() {
const [{ basket, user}, dispatch] = useStateValue();
const [orders, setOrders] = useState([]);
useEffect(() => {
const getDocs = async () => {
try {
const collRef = await getDocs(collection(db, "users", user?.id, "orders"));
// const collRef = doc(db, "users", user?.id, "orders"));
// const collRef = collection(db, "users", user?.id, "orders"));
const orderedRef = query(collRef, orderBy("created", "desc"));
const docSnap = onSnapshot(orderedRef, (snapshot) => {
snapshot.map((doc) => ({
id: doc.id,
data: doc.data(),
}));
setOrders(docSnap);
});
} catch (err) {
console.log(err.message);
}
};
getDocs();
}, [user]);
return (
<div className="orders">
<h3>Your Orders</h3>
<div className="orders__order">
{orders?.map((order, i) => (
<Order key={i} order={order} />
))}
</div>
</div>
);
}
export default Orders;
Console, screenshot

My guess it that user doesn't have a value, so user?.id becomes undefined. You'll want to still check whether user has a value inside the second snippet too.
For example:
useEffect(() => {
const getDocs = async (user: User) => {
try {
const collRef = await getDocs(collection(db, "users", user.id, "orders"));
...
} catch (err) {
console.log(err.message);
}
};
if (user) {
getDocs(user!);
}
}, [user]);

useEffect(() => {
if (user) {
try {
const collRef = collection(db, "users", user?.uid, "orders");
const orderedRef = query(collRef, orderBy("created", "desc"));
const docSnap = onSnapshot(orderedRef, (querySnapshot) => {
const orderArray = [];
querySnapshot.forEach((doc) => {
orderArray.push({
id: doc.id,
data: doc.data()
})
})
setOrders(orderArray);
});
} catch (err) {
console.log(err.message);
}
} else {
console.log("not found");
setOrders([]);
}
}, [user]);

Related

Filter the Firestore Query by Today's Date

Good Day,
Please help me figure out how to filter the firestore query to show only the data from today's date.
Here is my React code:
`
const History = () => {
const [codeexplainer, setExamples] = useState([]);
const [user] = useAuthState(auth);
const uid = user ? user.uid : "null";
useEffect(() => {
const fetchData = async () => {
const response = await fetch("/api/code/firebase-config");
const data = await response.json();
setExamples(data);
};
fetchData();
}, []);
const filteredData = useMemo(() => {
if (uid) {
return codeexplainer
.filter((result) => result.uid === uid && result !== undefined)
.sort((a, b) => b.timestamp - a.timestamp)
// .slice(0, 10);
}
return undefined;
}, [codeexplainer, uid])
const datafromfirebase = useMemo(() => {
if (filteredData && filteredData.length > 0) {
return filteredData.map((result) => (
<>
<Examples
code={result.code}
result={result.explanation}
/>
</>
))
} else {
return <p>Hey there! Add some code snippets and come back to see your history here.</p>
}
}, [filteredData])
`
Sample Firestore Data
This is the Firebase Config I am currently using.
Firebase Config:
import { initializeApp } from "firebase/app";
import { getFirestore } from "#firebase/firestore";
import {
collection,
addDoc,
getDocs,
serverTimestamp,
} from "firebase/firestore";
export const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: ""
};
const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
const resultsCollectionRef = collection(db, "codeexplainer");
const profaneCollectionRef = collection(db, "swear");
export default async function firebaseGet(req, res) {
if (req.method === "GET") {
const data = await getDocs(resultsCollectionRef);
const response = data.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
res.status(200).json(response);
}
}
export async function createFirebaseData(code, user, choices, userid) {
await addDoc(resultsCollectionRef, {
code: code,
user: user,
explanation: choices,
timestamp: serverTimestamp(),
uid: userid,
});
}
export async function getFirebaseData(req, res) {
const data = await getDocs(resultsCollectionRef);
const response = data.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
return response;
}
export async function loggedProfane(code, user, userid) {
await addDoc(profaneCollectionRef, {
code: code,
timestamp: serverTimestamp(),
user: user,
uid: userid,
});
}
Thank you.
I have tried the sorting and slice method but it's not working.
I'm new to React.
You are essentially fetching all the documents and the filtering them on the client side. If a user is supposed to read/write their documents only then this is not secure as anyone can read documents of all users and also inefficient as you are loading all the documents everytime just to show a subset. Instead you should use a query and security rules so a user can read their own data only. Try using this query:
import { query, collection, getDocs, where } from "firebase/firestore"
const startOfToday = new Date();
startOfToday.setUTCHours(0,0,0,0);
// Documents created today (after 00:00 UTC)
const q = query(resultsCollectionRef, where("timestamp", ">", startOfToday));
const data = await getDocs(resultsCollectionRef);
const response = data.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
You don't need any filtering or sorting on client side and can simply map the data into UI.

how to efficiently retrieve data from firebase/Firestore subcollection?

I'm using firestore to store posts each post could have simple properties such as {title: 'hi', comment: true} I'm able to easily fetch the user's specific posts since my collection structure looks like this: posts/user.id/post/post.name so an example will be posts/1234sofa/post/cool day
with this way of structuring, I'm able to easily fetch data for the user, but I'm having trouble with two things how do I fetch and display all posts for my main feed, and what's the most effective way of doing this? here is my current function for fetching user-specific data:
const submitpost = async () => {
try {
const collectionRef=collection(db,`posts`,user.uid.toString(),'post')
await addDoc(collectionRef, {
post: post,
timestamp: serverTimestamp(),
canComment: switchValue,
user: user.uid,
avatar: user.photoURL,
username: user.displayName,
});
toast({ title: "posted", status: "success", duration: 2000 });
} catch (error) {
console.log(error);
}
};
this specific function creates a structure like this in firebase posts are just takes and take is singular post respectively I just changed the name so its easier to understand:
now here is how im fetching the data for my spefic user:
const [user] = useAuthState(auth);
const [takes, settakes] = useState([]);
const getData = async () => {
// if user is present run function
if (user) {
// const docRef = doc(db, "users", user.uid);
// const collectionRef = collection(docRef, "takes");
// const querySnapshot = await getDocs(collectionRef);
try {
const docRef = doc(db, "posts", user.uid);
const collectionRef = collection(db,'posts',user.uid,'takes');
const querySnapshot = await getDocs(collectionRef);
const data = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data(),
}));
settakes(data);
} catch (error) {
console.log(error);
}
//
}
};
here is the function that doesn't work when fetching all data for main feed:
const [user]=useAuthState(auth)
const [allfeed, setallfeed] = useState([])
const getData = async () => {
if(user){
const collectionRef = collection(db, "posts");
const querySnapshot = await getDocs(collectionRef);
const data = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data(),
}));
// get data from firebase
setallfeed(data)
}
}
useEffect(() => {
getData()
console.log('ran');
console.log(allfeed);
// rerun when user is present
}, [user]);
when I console log the allfeed it returns an empty array so my main problem is how to do I get all the data from the posts collection meaning posts/userid/post/post.title I need to get these for every user. and secondly is there a more efficient way to structure my data?
I would suggest using the onSnapshot() method if you want realtime updates from a collection or a specific document.
setState() does not make changes directly to the state object. It just creates queues for React core to update the state object of a React component. If you add the state to the useEffect, it compares the two objects, and since they have a different reference, it once again fetches the items and sets the new items object to the state. The state updates then triggers a re-render in the component. And on, and on, and on...
If you just want to log your data into your console then you must use a temporary variable rather than using setState:
const getData = async () => {
if(user){
// Using `getDocs`
const collectionRef = collection(db, "posts");
const querySnapshot = await getDocs(collectionRef);
const data = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data(),
}));
console.log(data)
// ============================================= //
// Using `onSnapshot()`
const q = query(collection(db, "posts"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const data = querySnapshot.docs.map(d => ({
id: d.id,
...d.data()
}))
console.log(data)
});
}
}
useEffect(() => {
getData();
}, []);
You could also use multiple useEffect() to get the updated state of the object:
const getData = async () => {
if(user){
// Using `getDocs`
const collectionRef = collection(db, "posts");
const querySnapshot = await getDocs(collectionRef);
const data = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data(),
}));
setallfeed(data)
// ============================================= //
// Using `onSnapshot()`
const q = query(collection(db, "posts"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const data = querySnapshot.docs.map(d => ({
id: d.id,
...d.data()
}))
setallfeed(data)
});
}
}
useEffect(() => {
getData();
}, [])
useEffect(() => {
console.log(allfeed);
}, [allfeed]);
If you want to render it to the component then you should call the state in the component and map the data into it. Take a look at the sample code below:
const getData = async () => {
if(user){
// Using `getDocs`
const collectionRef = collection(db, "posts");
const querySnapshot = await getDocs(collectionRef);
const data = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data(),
}));
setallfeed(data)
// ============================================= //
// Using `onSnapshot()`
const q = query(collection(db, "posts"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const data = querySnapshot.docs.map(d => ({
id: d.id,
...d.data()
}))
setallfeed(data)
});
}
}
useEffect(() => {
getData()
}, []);
return (
<div>
<p>SomeData: <p/>
{items.map((item) => (
<p key={item.id}>{item.fieldname}</p>
))}
</div>
);
For more information you may checkout these documentations:
Get data with Cloud Firestore
Get realtime updates with Cloud Firestore

TypeError: doc is not a function at addSubCollectionDocument

I'm trying to add a new document to a non-existing sub-collection in a document(solutionsCollection/docID/commentsSubCollection).
But I'm getting this error message when I add a document to the sub-collection:
TypeError: doc is not a function at addSubCollectionDocument
Code to add a new document to a comments sub-collection:
const addSubCollectionDocument = async (docID, doc) => {
dispatch({ type: "IS_PENDING" })
try {
const docRef = doc(db, c, docID)
const colRef = collection(docRef, "comments")
const addedDocument = await addDoc(colRef, doc)
dispatchIfNotCancelled({ type: "ADDED_DOCUMENT", payload: addedDocument })
return addedDocument
} catch (error) {
console.log(error)
dispatchIfNotCancelled({ type: "ERROR", payload: error })
return null
}
}
handleSubmit function:
const handleSubmit = async (e) => {
e.preventDefault()
try {
const commentToAdd = {
id: Math.floor(Math.random() * 10000),
content: newComment.trim(),
reactions: [],
user: {
userID: user.uid,
avatarURL: user.photoURL,
displayName: user.displayName,
username: user.reloadUserInfo.screenName,
},
replies: [],
createdAt: new Date(),
}
await addSubCollectionDocument(id, commentToAdd)
setNewComment("")
if (response) {
console.log(response.error)
}
} catch (error) {
console.log(error)
}
}
useFirestore hook code:
import { useEffect, useReducer, useState } from "react"
import {
addDoc,
collection,
deleteDoc,
doc,
serverTimestamp,
updateDoc,
} from "firebase/firestore"
import { db } from "../firebase/config"
export const useFirestore = (c) => {
const [response, dispatch] = useReducer(firestoreReducer, initialState)
const [isCancelled, setIsCancelled] = useState(false)
// only dispatch is not cancelled
const dispatchIfNotCancelled = (action) => {
if (!isCancelled) {
dispatch(action)
}
}
// add a document
const addDocument = async (doc) => {
dispatch({ type: "IS_PENDING" })
try {
const createdAt = serverTimestamp()
const addedDocument = await addDoc(collection(db, c), {
...doc,
createdAt,
})
dispatchIfNotCancelled({ type: "ADDED_DOCUMENT", payload: addedDocument })
} catch (err) {
dispatchIfNotCancelled({ type: "ERROR", payload: err.message })
}
}
// Add document to sub collection
const addSubCollectionDocument = async (docID, doc) => {
dispatch({ type: "IS_PENDING" })
try {
const docRef = doc(db, c, docID)
const colRef = collection(docRef, "comments")
const addedDocument = await addDoc(colRef, doc)
dispatchIfNotCancelled({ type: "ADDED_DOCUMENT", payload: addedDocument })
return addedDocument
} catch (error) {
console.log(error)
dispatchIfNotCancelled({ type: "ERROR", payload: error })
return null
}
}
useEffect(() => {
return () => setIsCancelled(true)
}, [])
return {
addDocument,
addSubCollectionDocument,
response,
}
}
File where I'm importing addSubCollectionDocument hook:
import { useFirestore } from "../../hooks/useFirestore"
import { useAuthContext } from "../../hooks/useAuthContext"
const SolutionComments = ({ solution }) => {
const [newComment, setNewComment] = useState("")
const { user } = useAuthContext()
const { id } = useParams()
const { addSubCollectionDocument, response } = useFirestore("solutions")
const handleSubmit = async (e) => {
e.preventDefault()
try {
const commentToAdd = {
id: Math.floor(Math.random() * 10000),
content: newComment.trim(),
reactions: [],
user: {
userID: user.uid,
avatarURL: user.photoURL,
displayName: user.displayName,
username: user.reloadUserInfo.screenName,
},
replies: [],
createdAt: new Date(),
}
await addSubCollectionDocument(id, commentToAdd)
setNewComment("")
if (response) {
console.log(response.error)
}
} catch (error) {
console.log(error)
}
}
return (...)}
Package.json gist:
https://gist.github.com/rishipurwar1/57fbf348cd9755a940e7e3f218de570f
Firebase rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /challenges/{challenge}{
allow read: if true
}
match /resources/{resource}{
allow read: if true
}
match /solutions/{solution}{
allow read: if true
allow create: if request.auth.uid != null;
allow update, delete: if request.auth.uid == resource.data.userID;
}
match /users/{userId}{
allow create: if true
allow read: if true
}
}
}
const addSubCollectionDocument = async (docID, doc) => { ... })
// That "doc" parameter is not a function ^^^
That parameter seems to be an object that you are passing to addSubCollectionDocument(). Try renaming that to something else like:
const addSubCollectionDocument = async (docID, docData) => {
const colRef = collection(db, c, docID, "comments")
const addedDocument = await addDoc(colRef, doc)
})
You haven't specified security rules for your comments sub-collection. Try adding the following rules:
match /c/{docId}/comments/{commentId} {
allow read, write: if true;
}
Do update the if true to required rules as per your use case.

Uncaught ReferenceError: keyword is not defined at UseFetch.jsx and at Transactions.jsx

I am trying to load a website to my localhost but keep running into an error that says Uncaught ReferenceError: keyword is not defined at useFetch (:3000/src/hooks/useFetch.jsx:21:7) at TransactionsCard (:3000/src/components/Transactions.jsx:33:18). The issue hapens in my code where I'm fetching gifs from my API at giphy developer.
Here is the source code:
Transactions.jsx:
import React, { useEffect, useState } from "react";
import { ethers } from "ethers";
import { contractABI, contractAddress } from "../utils/constants";
export const TransactionContext = React.createContext();
const { ethereum } = window;
const createEthereumContract = () => {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const transactionsContract = new ethers.Contract(contractAddress, contractABI, signer);
return transactionsContract;
};
export const TransactionsProvider = ({ children }) => {
const [formData, setformData] = useState({ addressTo: "", amount: "", keyword: "", message: "" });
const [currentAccount, setCurrentAccount] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [transactionCount, setTransactionCount] = useState(localStorage.getItem("transactionCount"));
const [transactions, setTransactions] = useState([]);
const handleChange = (e, name) => {
setformData((prevState) => ({ ...prevState, [name]: e.target.value }));
};
const getAllTransactions = async () => {
try {
if (ethereum) {
const transactionsContract = createEthereumContract();
const availableTransactions = await transactionsContract.getAllTransactions();
const structuredTransactions = availableTransactions.map((transaction) => ({
addressTo: transaction.receiver,
addressFrom: transaction.sender,
timestamp: new Date(transaction.timestamp.toNumber() * 1000).toLocaleString(),
message: transaction.message,
keyword: transaction.keyword,
amount: parseInt(transaction.amount._hex) / (10 ** 18)
}));
console.log(structuredTransactions);
setTransactions(structuredTransactions);
} else {
console.log("Ethereum is not present");
}
} catch (error) {
console.log(error);
}
};
const checkIfWalletIsConnect = async () => {
try {
if (!ethereum) return alert("Please install MetaMask.");
const accounts = await ethereum.request({ method: "eth_accounts" });
if (accounts.length) {
setCurrentAccount(accounts[0]);
getAllTransactions();
} else {
console.log("No accounts found");
}
} catch (error) {
console.log(error);
}
};
const checkIfTransactionsExists = async () => {
try {
if (ethereum) {
const transactionsContract = createEthereumContract();
const currentTransactionCount = await transactionsContract.getTransactionCount();
window.localStorage.setItem("transactionCount", currentTransactionCount);
}
} catch (error) {
console.log(error);
throw new Error("No ethereum object");
}
};
const connectWallet = async () => {
try {
if (!ethereum) return alert("Please install MetaMask.");
const accounts = await ethereum.request({ method: "eth_requestAccounts", });
setCurrentAccount(accounts[0]);
window.location.reload();
} catch (error) {
console.log(error);
throw new Error("No ethereum object");
}
};
const sendTransaction = async () => {
try {
if (ethereum) {
const { addressTo, amount, keyword, message } = formData;
const transactionsContract = createEthereumContract();
const parsedAmount = ethers.utils.parseEther(amount);
await ethereum.request({
method: "eth_sendTransaction",
params: [{
from: currentAccount,
to: addressTo,
gas: "0x5208", //21,000 gwei in hexadecimal form
value: parsedAmount._hex,
}],
});
const transactionHash = await transactionsContract.addToBlockchain(addressTo, parsedAmount, message, keyword);
setIsLoading(true);
console.log(`Loading - ${transactionHash.hash}`);
await transactionHash.wait();
console.log(`Success - ${transactionHash.hash}`);
setIsLoading(false);
const transactionsCount = await transactionsContract.getTransactionCount();
setTransactionCount(transactionsCount.toNumber());
window.location.reload();
} else {
console.log("No ethereum object");
}
} catch (error) {
console.log(error);
throw new Error("No ethereum object");
}
};
useEffect(() => {
checkIfWalletIsConnect();
checkIfTransactionsExists();
}, [transactionCount]);
return (
<TransactionContext.Provider
value={{
transactionCount,
connectWallet,
transactions,
currentAccount,
isLoading,
sendTransaction,
handleChange,
formData,
}}
>
{children}
</TransactionContext.Provider>
);
};
useFetch.jsx:
import { useEffect, useState } from 'react';
const API_KEY = import.meta.env.VITE_GIPHY_API;
const useFetch = () => {
const [gifUrl, setGifUrl] = useState("");
const fetchGifs = async () => {
try {
const response = await fetch(`https://api.giphy.com/v1/gifs/search?api_key=${API_KEY}&q=${keyword.split(" ").join("")}&limit=1`)
const { data } = await response.json();
setGifUrl(data[0]?.images?.downsized_medium?.url)
} catch (error) {
setGifUrl('https://metro.co.uk/wp-content/uploads/2015/05/pokemon_crying.gif?quality=90&strip=all&zoom=1&resize=500%2C284')
}
}
useEffect(() => {
if (keyword) fetchGifs();
}, [keyword]);
return gifUrl;
}
export default useFetch;
When I comment out the lines that use 'keyword' it launches with no errors. This is at lines 14, 18, and 58-62 of Transactions.jsx.
Any help would be greatly appreciated, thank you!
The problem here is that you have not define keyword inside your useFetch function.
If you are trying to pass the keyword from the place where you use useFetch then do something like below and use the useFetch like const gifUrl = useFetch(<keyword>)
import { useEffect, useState } from 'react';
const API_KEY = import.meta.env.VITE_GIPHY_API;
const useFetch = (keyword) => {
const [gifUrl, setGifUrl] = useState("");
const fetchGifs = async () => {
try {
const response = await fetch(`https://api.giphy.com/v1/gifs/search?api_key=${API_KEY}&q=${keyword.split(" ").join("")}&limit=1`)
const { data } = await response.json();
setGifUrl(data[0]?.images?.downsized_medium?.url)
} catch (error) {
setGifUrl('https://metro.co.uk/wp-content/uploads/2015/05/pokemon_crying.gif?quality=90&strip=all&zoom=1&resize=500%2C284')
}
}
useEffect(() => {
if (keyword) fetchGifs();
}, [keyword]);
return gifUrl;
}
export default useFetch;
or even try adding default value for key work like below.
const useFetch = (keyword = "some keyword") => {

Cannot read property OnSnapshot of undefined

componentDidMount() {
this.authListener = auth.onAuthStateChanged( async userAuth => {
if(userAuth){
const userRef = await handleUserProfile(userAuth); userRef.onSnapshot(snapshot=>{
this.setState({ currentUser:{ id:snapshot.id, ...snapshot.data() } }) }) } this.setState({ ...initialState })
});
}
Your function handleUserProfile is not returning the userRef so you get that error. It would be great to provide more that to help you more with this. Maybe the code of that function handleUserProfile.
Can you try to updated your handleUserProfile function to:
export const handleUserProfile = async ({ userAuth, additionalData = {} }) => {
if (!userAuth) {
console.warn("No userAuth provided!");
return;
}
const { uid } = userAuth;
const userRef = firestore.doc(`users/${uid}`);
const snapshot = await userRef.get();
if (!snapshot.exists) {
const { displayName, email } = userAuth;
const timestamp = new Date();
try {
await userRef.set({
displayName,
email,
createdDate: timestamp,
...additionalData,
});
} catch (err) {
console.warn(err);
}
}
return userRef;
};
If you are getting the log No userAuth provided! you are not sending a userAuth to it.

Categories

Resources