currentUser uid undefined with getAuth hook - reactjs

I'm a freshman in college and currently beginning with react and firebase in my free time. There is one thing I don't know why it doesn't works in my project.
const currentUser = useAuth()
const { documents: books } = useCollection("books", ["uid", "==", currentUser.uid])
the problem is that when i console i get ["uid", "==", undefined]
This is my useAuth hook
import { useState, useEffect } from 'react'
import { onAuthStateChanged } from "firebase/auth";
import { auth } from '../firebase/config'; //this is getAuth()
export function useAuth() {
const [currentUser, setCurrentUser] = useState();
useEffect(() => {
const unsub = onAuthStateChanged(auth, (user) => setCurrentUser(user));
return unsub;
}, [])
return currentUser;
}
and this is my hook to collect data from firestore
import { useState, useEffect, useRef } from "react"
import { db } from "../firebase/config" //this is getFirestore()
//firebase imports
import { collection, onSnapshot, query, where} from "firebase/firestore"
export const useCollection = (col, _q) => {
const [error, setError] = useState(null)
const [documents, setDocuments] = useState(null)
//set up query
const q = useRef(_q).current
useEffect(() => {
setError(null)
let ref = collection(db, col)
if (q) {
ref = query(ref, where(...q))
}
const unsub = onSnapshot(ref, (snapshot) => {
let results = []
snapshot.docs.forEach(doc => {
results.push({ id: doc.id, ...doc.data() })
})
setDocuments(results)
}, (err) => {
console.log(err.message)
setError(err.message)
})
return () => unsub()
}, [col, q])
return { documents, error }
}
I thought about something with sync or async, but could not find it.
Would someone have a solution and explain it to me?

Related

Blank page after refresh react firebase authentication

I have the above firebase db. I want to extract the displayName value and use it in a greeting message after the user is successfully login (e.g. Hello George!). I manage to achieve this but when I refresh the page everything disappears and in console I get this error "index.esm2017.js:1032 Uncaught TypeError: Cannot read properties of undefined (reading 'indexOf')".
Is this a problem of how I extract the displayName from firebase document?
Can someone explain to me what is the problem, please?
Here is my code:
AuthContext.js
import { createContext, useContext, useEffect, useState } from "react";
import {
onAuthStateChanged,
signInWithEmailAndPassword,
signOut,
} from "firebase/auth";
import { auth } from "../utils/firebase/firebase.utils";
const UserContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState({});
const signIn = (email, password) =>
signInWithEmailAndPassword(auth, email, password);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
console.log(currentUser);
setUser(currentUser);
});
return () => unsubscribe();
}, []);
const logOut = () => signOut(auth);
return (
<UserContext.Provider value={{ user, signIn, logOut }}>
{children}
</UserContext.Provider>
);
};
export const UserAuth = () => {
return useContext(UserContext);
};
WelcomePage.jsx
import React, { useState, useEffect } from "react";
import { UserAuth } from "../contexts/AuthContext";
import { db } from "../utils/firebase/firebase.utils";
import { doc, Firestore, getDoc } from "firebase/firestore";
const WelcomePage = () => {
const [userDetails, setUserDetails] = useState({});
const { user } = UserAuth();
useEffect(() => {
const docRef = doc(db, "users", user.uid);
const fetchData = async () => {
try {
const docSnap = await getDoc(docRef);
setUserDetails(docSnap.data());
console.log(docSnap.data());
} catch (e) {
console.log(e);
}
};
fetchData();
}, [user]);
return (
<div>
<h1>Hello, {userDetails.displayName}!</h1>
</div>
);
};
export default WelcomePage;
You might only want to fetch documents in the WelcomePage component if there's a truthy uid value to use.
const { user } = UserAuth();
useEffect(() => {
const fetchData = async () => {
const docRef = doc(db, "users", user.uid);
try {
const docSnap = await getDoc(docRef);
setUserDetails(docSnap.data());
console.log(docSnap.data());
} catch (e) {
console.log(e);
}
};
if (user?.id) {
fetchData();
}
}, [user]);

Uploading Images to Firebase through a React App

I created a photo gallery app using React. When I upload an Image in the App, it is storing two documents of the image in the firestore, containing exactly the same 'created At' and 'url', but under two difference IDs. I want it to store only 1 document. I am not able to understand why my code is running twice and uploading the image twice?
Uploading is done using 'useStorage' custom hook:
import { useState, useEffect } from 'react'
import { projectStorage, projectFirestore, timestamp } from '../firebase/config'
const useStorage = (file) => {
const [progress, setProgress] = useState(0)
const [error, setError] = useState(null)
const [url, setUrl] = useState(null)
useEffect(() => {
// references
const storageRef = projectStorage.ref(file.name)
const collectionRef = projectFirestore.collection('images')
// uploading the file to the reference
storageRef.put(file).on(
'state_changed',
(snap) => {
let percentage = (snap.bytesTransferred / snap.totalBytes) * 100
setProgress(percentage)
},
(err) => {
setError(err)
},
async () => {
const url = await storageRef.getDownloadURL()
const createdAt = timestamp()
await collectionRef.add({ url, createdAt })
setUrl(url)
}
)
}, [file])
return { progress, url, error }
}
export default useStorage
'useFirestore' custom hook:
import { useEffect, useState } from 'react'
import { projectFirestore } from '../firebase/config'
const useFirestore = (collection) => {
const [docs, setDocs] = useState([])
useEffect(() => {
// return a function to un-subscribe from the collection
const unsub = projectFirestore
.collection(collection)
.orderBy('createdAt', 'desc')
.onSnapshot((snap) => {
let documents = []
snap.forEach((doc) => {
documents.push({ ...doc.data(), id: doc.id })
})
setDocs(documents)
})
// clean-up function
return () => unsub()
}, [collection])
return { docs }
}
export default useFirestore
Remove Strict Mode in index.js file, that is causing upload of file 2 times.

custom hooks return value doesn't change in Component

My custom hook fetches data asynchronously. When it is used in a component, returned value doesn't get updated. It keeps showing default value. Does anybody know what is going on? Thank you!
import React, {useState, useEffect} from 'react'
import { getDoc, getDocs, Query, DocumentReference, deleteDoc} from 'firebase/firestore'
export const useFirestoreDocument = <T>(docRef: DocumentReference<T>) => {
const [value, setValue] = useState<T|undefined>(undefined)
const [isLoading, setIsLoading] = useState<boolean>(true)
const update = async () => {
const docSnap = await getDoc(docRef)
if (docSnap.exists()) {
const data = docSnap.data()
setValue(data)
}
setIsLoading(false)
}
useEffect(() => {
update()
}, [])
console.log(value, isLoading) // it can shows correct data after fetching
return {value, isLoading}
}
import { useParams } from 'react-router-dom'
const MyComponent = () => {
const {userId} = useParams()
const docRef = doc(db, 'users', userId!)
const {value, isLoading} = useFirestoreDocument(docRef)
console.log(value, isLoading) // keeps showing {undefined, true}.
return (
<div>
...
</div>
)
}
It looks like youe hook is only being executed once upon rendering, because it is missing the docRef as a dependency:
export const useFirestoreDocument = <T>(docRef: DocumentReference<T>) => {
const [value, setValue] = useState<T|undefined>(undefined)
const [isLoading, setIsLoading] = useState<boolean>(true)
useEffect(() => {
const update = async () => {
const docSnap = await getDoc(docRef)
if (docSnap.exists()) {
const data = docSnap.data()
setValue(data)
}
setIsLoading(false)
}
update()
}, [docRef])
console.log(value, isLoading) // it can shows correct data after fetching
return {value, isLoading}
}
In addition: put your update function definition inside the useEffect hook, if you do not need it anywhere else. Your linter will complaing about the exhaustive-deps rule otherwise.
The useEffect hook is missing a dependency on the docRef:
export const useFirestoreDocument = <T>(docRef: DocumentReference<T>) => {
const [value, setValue] = useState<T|undefined>(undefined);
const [isLoading, setIsLoading] = useState<boolean>(true);
useEffect(() => {
const update = async () => {
setIsLoading(true);
try {
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
const data = docSnap.data();
setValue(data);
}
} catch(error) {
// handle any errors, log, etc...
}
setIsLoading(false);
};
update();
}, [docRef]);
return { value, isLoading };
};
The render looping issue is because docRef is redeclared each render cycle in MyComponent. You should memoize this value so a stable reference is passed to the useFirestoreDocument hook.
const MyComponent = () => {
const {userId} = useParams();
const docRef = useMemo(() => doc(db, 'users', userId!), [userId]);
const {value, isLoading} = useFirestoreDocument(docRef);
console.log(value, isLoading);
return (
<div>
...
</div>
);
};

I got 'Too many re-renders' while converting my axios codes to react-query

It seems like react-query is a quiet popular so, I trying to add react-query to my exist codes.
the code below is the exist codes. it uses hooks (useEffect & useState), axios and returns response data.
import { useState, useEffect } from 'react';
import { apiProvider } from 'services/modules/provider';
import { useLoading } from 'components/Loading/Loading';
export const useCommonApi = (url: string, params?: any) => {
const [_, setLoading] = useLoading();
const [State, setState] = useState<any>();
useEffect(() => {
try {
const getState = async () => {
const result: any = await apiProvider.get('common/' + url, params);
let resultData = result.data || [];
if (url === 'available_countries') {
resultData = resultData.map((o: any) => {
return { value: o.id, label: o.name };
});
}
setState([...resultData]);
return resultData;
};
getState();
} catch (e) {
console.error(e);
}
}, []);
return State;
};
Here is the my new codes for react-query. I am trying to convert code above into react-query as below.
import { useState, useEffect } from 'react';
import { apiProvider } from 'services/modules/provider';
import { useLoading } from 'components/Loading/Loading';
import axios from 'axios';
import { useQuery } from 'react-query';
export const useCommonApi_adv = (url: string, params?: any) => {
const [_, setLoading] = useLoading();
const [State, setState] = useState<any>();
const { isLoading, error, data } = useQuery('fetchCommon', () =>
axios('/api/v1/admin/common/' + url).then( (res) :any => {
return res.data
})
)
if (isLoading) return 'Loading...'
let resultData = data.data || [];
if (url === 'available_countries') {
resultData = resultData.map((o: any) => {
return { value: o.id, label: o.name };
});
}
setState([...resultData]);
return State;
};
the my new codes(react-query) prints "too many render" when it is executed.
What did I wrong with it? any help please
You are calling your state update function setState outside of an useEffect. This will run on the first render, update the state, which in turn triggers a rerender, update the state again and you end up in an endless loop. You probably want to wrap that logic into useEffect and only run it if data changes.
import { useState, useEffect } from 'react';
import { apiProvider } from 'services/modules/provider';
import { useLoading } from 'components/Loading/Loading';
import axios from 'axios';
import { useQuery } from 'react-query';
export const useCommonApi_adv = (url: string, params?: any) => {
const [_, setLoading] = useLoading();
const [State, setState] = useState<any>();
const { isLoading, error, data } = useQuery('fetchCommon', () =>
axios('/api/v1/admin/common/' + url).then( (res) :any => {
return res.data
})
)
useEffect(() => {
let resultData = data.data || [];
if (url === 'available_countries') {
resultData = resultData.map((o: any) => {
return { value: o.id, label: o.name };
});
}
setState([...resultData]);
}, [data])
if (isLoading) return 'Loading...'
return State;
};

How can I call function in React native context file before another function is called?

I'm trying to get my current position and to get some cafe lists around me.
I made a getLocation function and I import it inside of my Context file CafeContext. However, I can't get the position before getting the cafe list.
It works sometimes when I set the lat/long in the range [37.~~, 125.~~].
This is getLocation
import { useState, useEffect } from "react";
import * as Location from "expo-location";
const getLocation = () => {
const [myX, setMyX] = useState(0);
const [myY, setMyY] = useState(0);
try {
const currentLocation = async () => {
await Location.requestPermissionsAsync();
const coordsObj = await Location.getCurrentPositionAsync();
await setMyY(coordsObj.coords.latitude);
await setMyX(coordsObj.coords.longitude);
};
useEffect(() => {
currentLocation();
}, []);
return { myX, myY };
} catch (err) {
setMyY(37.5572);
setMyX(126.9279);
return { myX, myY };
}
};
export default getLocation;
And this is CafeContext:
import React, { useState, createContext } from "react";
import cafeApi from "../api/cafeApi";
import AsyncStorage from "#react-native-community/async-storage";
import testArray from "../api/testArray.json";
import { navigate } from "../RootNavigation";
import getLocation from "../hooks/getLocation";
const CafeContext = React.createContext();
export const CafeProvider = ({ children }) => {
const [cafeList, setCafeList] = useState([]);
const [errorMessage, setErrorMessage] = useState("");
const [distance, setDistance] = useState(300);
//#####This line. I want to get location before getCafeList...
const { myX, myY } = getLocation();
const getCafeList = async () => {
const response = await cafeApi.get("/search", {
params: {
// category_group_code: "CE7",
x: myX,
y: myY,
radius: distance,
},
});
await setCafeList(response.data);
};
return (
<CafeContext.Provider
value={{
cafeList,
getCafeList,
distance,
setDistance,
term,
setTerm,
searchCafeList,
getLikedCafeList,
}}
>
{children}
</CafeContext.Provider>
);
};
export default CafeContext;
Your logic in getLocation is wrong. You are using hooks incorrectly, you can't return a values from a component.
If you want to return values you should create a custom hook also stop awaiting setState functions.
Custom hooks.
import { useState, useEffect } from 'react';
function useLocation() {
const [myX, setMyX] = useState(37.5572);
const [myY, setMyY] = useState(126.9279);
useEffect(() => {
const currentLocation = async () => {
await Location.requestPermissionsAsync();
const coordsObj = await Location.getCurrentPositionAsync();
setMyY(coordsObj.coords.latitude);
setMyX(coordsObj.coords.longitude);
};
currentLocation();
}, []);
return return { myX, myY };
}
Context
const CafeContext = React.createContext();
export const CafeProvider = ({ children }) => {
const [cafeList, setCafeList] = useState([]);
const [errorMessage, setErrorMessage] = useState("");
const [distance, setDistance] = useState(300);
//#####This line. I want to get location before getCafeList...
const { myX, myY } = useLocation();
const getCafeList = async () => {
const response = await cafeApi.get("/search", {
params: {
// category_group_code: "CE7",
x: myX,
y: myY,
radius: distance,
},
});
setCafeList(response.data);
};
return (
<CafeContext.Provider
value={{
cafeList,
getCafeList,
distance,
setDistance,
term,
setTerm,
searchCafeList,
getLikedCafeList,
}}
>
{children}
</CafeContext.Provider>
);
};
export default CafeContext;

Resources