I'm trying to access a single user data from firebase by passing currentUserId to useCollection hook but currentUser?.id is not available when the application first loads.
this is how I calling my hooks from a component.
const { currentUser } = useAuth()
const { docs = [] } = useCollection("solutions", currentUser?.id, false)
useCollection hook:
const useCollection = (collection, _userID, _openTab) => {
const [docs, setDocs] = useState([])
const [loading, setLoading] = useState(true)
const userID = useRef(_userID).current
const openTab = useRef(_openTab).current
// getting realtime data from the firebase for challenges and solutions
useEffect(() => {
let ref = firestore.collection(collection)
console.log(_userID)
if (openTab && userID) {
console.log("open")
openTab === 1
? (ref = ref.where("userID", "==", userID).where("completed", "==", false))
: (ref = ref.where("userID", "==", userID).where("completed", "==", true))
}
if (userID) {
ref = ref.where("userID", "==", userID)
console.log("first")
}
const unsubscribe = ref.onSnapshot(
(snapshot) => {
const results = []
snapshot.docs.forEach((doc) => {
results.push({ ...doc.data(), id: doc.id })
})
// update state
setDocs(results)
setLoading(false)
},
(error) => {
console.log(error)
}
)
// unsubscribe on unmount
return () => unsubscribe()
}, [collection, openTab, loading]) // eslint-disable-line react-hooks/exhaustive-deps
return { docs, loading }
}
Anyone please help me with this!
Related
When a user updates their profile photo I want old data to be overwritten on firestore. I'm using firebase storage to store photos and upload firebase URL to restore database so
I tried filtering in on the front end side but I have multiple users to filter and there are a lot of duplicates
here is whole functionality of uploading data to firestore storage then updating firestore db and then pulling data with use
const [userImg, setUserImg] = useState()
const [image, setImage] = useState(null)
const [htlmImg, setHtmlImg] = useState(null)
const [url, setUrl] = useState(null)
const [userName, setUserName] = useState(null)
const [sureLoading, setSureLoading] = useState(false)
const [photoEdit, setPhotoEdit] = useState(false)
const handleImageChange = (e) => {
if (e.target.files[0]) {
setImage(e.target.files[0])
setHtmlImg(URL.createObjectURL(e.target.files[0]))
}
}
const uploadImg = () => {
const imageRef = ref(storage, `image${user.uid}`)
uploadBytes(imageRef, image)
.then(() => {
getDownloadURL(imageRef)
.then((url) => {
setUrl(url)
})
.catch((error) => {
console.log(error.message, 'error getting the image url')
})
setImage(null)
})
.catch((error) => {
console.log(error.message)
})
setSureLoading(true)
}
const handlePfpSubmit = async () => {
const { uid } = user
if (url !== null) {
try {
await addDoc(collection(db, 'user'), {
pfp: url,
userName,
uid,
timestamp: serverTimestamp(),
time: Date(),
})
if (!photoEdit) {
navigate('/test')
}
console.log('data send')
} catch (err) {
console.log(err)
}
}
}
const [displayName, setDisplayName] = useState(null)
const [displayPhoto, setDisplayPhoto] = useState(null)
const [userProfiles, setUserProfiles] = useState(null)
useEffect(() => {
const q = query(collection(db, 'user'),
orderBy('timestamp')).update()
const unsub = onSnapshot(q, (querySnapShot) => {
let photo = []
querySnapShot.forEach((doc) => {
photo.push({ ...doc.data(), id: doc.id })
})
console.log(photo)
console.log('data resived')
let userUid = photo
.filter((item) => {
if (user.uid === item.uid) {
return item.uid
}
})
.map((item) => {
const { pfp } = item
return pfp
})
setDisplayPhoto(
userUid.filter((val, index) => {
if (userUid.length - 1 <= index) {
return val
}
}),
)
let userUidName = photo
.filter((item) => {
if (user.uid === item.uid) {
return item.uid
}
})
.map((item) => {
const { userName } = item
return userName
})
let photoFilter = userUidName.filter((val, index) => {
if (userUidName.length - 1 <= index) {
return val
}
})
setDisplayName(photoFilter)
setUserProfiles(photo)
console.log(displayPhoto)
})
console.log('re render ? ')
return () => unsub()
}, [user])
Gets list of emails from firestore and checks if current user is registered and then redirects them to sign up if they are new user.
The code is functional(it redirects succesfully) but get the following error:
arning: Cannot update a component (BrowserRouter) while rendering a different component You should call navigate() in a React.useEffect(), not when your component is first rendered.
const navigate = useNavigate();
let hasEmail = false;
const [emailList, setEmailList] = useState([]);
const emailRef = collection(db, "emails");
useEffect(() => {
const getEmails = async () => {
const data = await getDocs(emailRef);
setEmailList(
data.docs.map((doc) => ({
...doc.data(),
}))
);
};
getEmails();
}, []);
const emailCheck = (emails) => { //checks if email exists
hasEmail = emails.some((e) => e.email === auth.currentUser.email);
};
const direct = () => { // redirects to required page
if (hasEmail) {
navigate("/index");
} else {
navigate("/enterdetails");
}
};
emailCheck(emailList);
direct();
Move the email checking logic into a useEffect hook with a dependency on the emailList state.
const navigate = useNavigate();
const [emailList, setEmailList] = useState([]);
const emailRef = collection(db, "emails");
useEffect(() => {
const getEmails = async () => {
const data = await getDocs(emailRef);
setEmailList(
data.docs.map((doc) => ({
...doc.data(),
}))
);
};
getEmails();
}, []);
useEffect(() => {
if (emailList.length) {
const hasEmail = emailList.some((e) => e.email === auth.currentUser.email);
navigate(hasEmail ? "/index" : "/enterdetails");
}
}, [auth, emailList, navigate]);
This might not run without the proper firebase config but check it out
https://codesandbox.io/s/elated-bell-kopbmp?file=/src/App.js
Things to note:
Use useMemo for hasEmail instead of emailCheck. This will re-run only when emailList changes
const hasEmail = useMemo(() => {
//checks if email exists
return emailList.some((e) => e.email === auth.currentUser.email);
}, [emailList]);
There isn't really a point in having this in a react component if you are just redirecting away. Consider having the content of 'index' at the return (</>) part of this component. Only redirect if they aren't authorized
useEffect(() => {
if (!hasEmail) {
navigate("/enterdetails");
}
//else {
// navigate("/index");
//}
}, [hasEmail, navigate]);
Please I need assistance with a code.
I have a Nextjs dynamic page as my 'stripe_success_url'
Initially I had one product 'courses' whose {id} is beign fetched in the router.query
however, i have created a new product 'ebook' whose {id} is also being fetched in the router.query
I edited the code to differentiate course ID and ebook ID but it is not working yet.
const StripeSuccess = () => {
// state
const [course, setCourse] = useState([]);
const [ebook, setEbook] = useState([]);
const router = useRouter();
const { id } = router.query;
useEffect(() => {
const fetchCourses = async () => {
const { data } = await axios.get('/api/courses');
setCourse(data);
//console.log(data);
};
fetchCourses();
}, [id]);
useEffect(() => {
const fetchEbooks = async () => {
const { data } = await axios.get('/api/ebooks');
setEbook(data);
//console.log(data);
};
fetchEbooks();
}, [id]);
useEffect(() => {
if (id === `${course._id}`) {
const successRequest = async () => {
const { data } = await axios.get(`/api/stripe-success/${id}`);
//console.log(data);
//console.log('SUCCESS REQ DATA', data);
router.push(`/user/course/${data.course.slug}`);
};
successRequest();
}
}, [id]);
useEffect(() => {
if (id === `${ebook._id}`) {
const successEbookRequest = async () => {
const { data } = await axios.get(`/api/stripe-ebooksuccess/${id}`);
//console.log(data);
//console.log('SUCCESS REQ DATA', data);
router.push(`/user/course/${data.ebook.slug}`);
};
successEbookRequest();
}
}, [id]);
I want to get extra data from a users collection in firestore when user loggs in. I do this in a useEffect function in a AuthContext. This is my code:
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
const fetchUserData = async () => {
if (!user) {
setCurrentUser(null);
setLoading(false);
return;
}
const userData = await fetchUserDataFromFirestore(user.uid);
setCurrentUser({ ...user, ...userData });
setLoading(false);
};
fetchUserData();
});
return unsubscribe;
}, [currentUser]);
This kind of works as I do get the data but messages are piling up in the console as can be seen in my screenshot:
The fetchUserDataFromFirestore function is implemented like this:
export const fetchUserDataFromFirestore = async (id) => {
const docRef = doc(db, "users", id);
const docSnap = await getDoc(docRef);
if (docSnap.exists) {
const userData = docSnap.data();
return userData;
}
return null;
};
What can I do about this?
For future reference this is how I did it
const [uid, setUid] = useState(null)
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async (user) => {
if (user) {
setUid(user.uid)
} else {
setUid(null)
}
})
return () => {
unsubscribe()
}
}, [])
// set currentUser state
useEffect(() => {
if (uid) {
const userRef = doc(db, "users", uid)
getDoc(userRef)
.then((docSnapshot) => {
const data = docSnapshot.data()
setCurrentUser(data)
})
}
}, [uid])
I wanted to re-render my component and update array of events when filters are changing.
const filters = useSelector(state => state.mapRedux.filters)
const [allEvents, setAllEvents] = useState([]);
const getAllEvents = async (start) => {
let myEventsArray = [];
await firebase.firestore().collection('wydarzenie')
.where('sport', 'in', createFiltersTable())
.where('miasto', '==', currentCity)
.orderBy("data_rozpoczecia")
.startAfter(start)
.limit(limit)
.get().then(snapshot => {
if (snapshot.size < limit) setShowMore(false)
snapshot.forEach(doc => {
let info = doc.data()
let el = {
id: doc.id,
...info
}
myEventsArray.push(el)
});
})
let new_array = allEvents.concat(myEventsArray)
setAllEvents(new_array);
}
useEffect(() => {
setAllEvents([])
getAllEvents(new Date());
}, [filters])
And that works, but I don't why setAllEvents([]) doesn't clear my events array. Instead new array is joins with old one and I get duplicate of some elements.
Here is what you can do to prevent stale closures but not run the effect too many times:
const AllEvents = (props) => {
const currentCity = useSelector(
(state) => state.mapRedux.city.name
);
const [allEvents, setAllEvents] = useState([]);
const [limit, setLimit] = useState(6);
const [showMore, setShowMore] = useState(true);
// filtry
const filters = useSelector(
(state) => state.mapRedux.filters
);
const createFiltersTable = React.useCallback(() => {
const tmp = Object.values(filters);
const values = [];
tmp.map((el) => {
if (el.active) values.push(el.events_name);
});
return values;
}, [filters]); //re create this function when filters change
const getAllEvents = React.useCallback(
async (start) => {
let myEventsArray = [];
await firebase
.firestore()
.collection('wydarzenie')
.where('sport', 'in', createFiltersTable())
.where('miasto', '==', currentCity)
.orderBy('data_rozpoczecia')
.startAfter(start)
.limit(limit)
.get()
.then((snapshot) => {
if (snapshot.size < limit) setShowMore(false);
snapshot.forEach((doc) => {
let info = doc.data();
let el = {
id: doc.id,
...info,
};
myEventsArray.push(el);
});
});
setAllEvents((allEvents) =>
//use callback to prevent allEvents being a dependency
allEvents.concat(myEventsArray)
);
},
//re create getAllEvents when createFiltersTable, currentCity
// or limit changes
[createFiltersTable, currentCity, limit]
);
useEffect(() => {
setAllEvents([]);
getAllEvents(new Date());
//effect will run when filters change or when
// getAllEvents change, getAllEvents will change
// when filters, currentCity or limit changes
}, [filters, getAllEvents]);
return ...;
};