I'm doing a mobile application using React Native (0.61.2) and TypeScript (3.6.4). I'm using Firebase Authentication and Firebase Firestore from React Native Firebase collection.
I'm trying to:
- Listening Auth state changes (using onAuthStateChanged)
- Listening user auth changes (using onUserChanged)
- Listening user's document (using onSnapshot)
- Listening a specific document called brand (using onSnapshot)
I'm saving in the Context the userAuth, user document and the brand document from the previous listeners.
/**
* Types
*/
type UserAuth = FirebaseAuthTypes.User | null;
type User = UserDoc | null;
type Brand = BrandDoc | null;
type ContextProps = {
userAuth: UserAuth;
user: User;
brand: Brand;
} | null;
/**
* Context
*/
export const Context = createContext<ContextProps>(null);
function App() {
const [initializing, setInitializing] = useState(true);
const [listenUserAuth, setListenUserAuth] = useState(false);
const [userAuth, setUserAuth] = useState<UserAuth>(null);
const [listenUser, setListenUser] = useState(false);
const [user, setUser] = useState<User>(null);
const [listenBrand, setListenBrand] = useState(false);
const [brand, setBrand] = useState<Brand>(null);
/** Listen for auth state changes */
useEffect(() => {
const authListener = auth().onAuthStateChanged(result => {
setUserAuth(result);
if (initializing && !listenUserAuth) {
setInitializing(false);
setListenUserAuth(true);
setListenUser(true);
setListenBrand(true);
}
});
return () => {
if (authListener) {
console.log('removing auth state listener');
authListener();
}
};
}, [initializing, listenUserAuth]);
/** Listen for user auth changes */
useEffect(() => {
let userAuthListener: () => void;
if (listenUserAuth) {
userAuthListener = auth().onUserChanged(result => {
setUserAuth(result);
});
}
return () => {
if (userAuthListener) {
console.log('removing user auth listener');
userAuthListener();
}
};
}, [listenUserAuth]);
/** Listen for user document changes */
useEffect(() => {
let userListener: () => void;
if (listenUser) {
if (!userAuth) {
return;
}
console.log('listening user document');
userListener = firestore()
.collection('users')
.doc(userAuth.uid)
.onSnapshot(querySnapshot => {
console.log('User querySnapshot: ', querySnapshot.data());
setUser(querySnapshot.data());
});
}
return () => {
if (userListener) {
console.log('removing user document listener');
userListener();
}
};
}, [listenUser, userAuth]);
/** Listen for brand document changes */
useEffect(() => {
let brandListener: () => void;
if (listenBrand) {
if (!userAuth) {
return;
}
console.log('listening brand document');
brandListener = firestore()
.collection('brands')
.doc('31uOUtUkVYg8z953UdxS')
.onSnapshot(querySnapshot => {
console.log('Brand querySnapshot: ', querySnapshot.data());
setBrand(querySnapshot.data());
});
}
return () => {
if (brandListener) {
console.log('removing brand document listener');
brandListener();
}
};
}, [listenBrand, userAuth]);
if (initializing) {
return (
<View style={{flex: 1, justifyContent: 'center'}}>
<ActivityIndicator size="large" color="#000" />
</View>
);
}
function container(children: ReactNode | ReactNode[]) {
return <PaperProvider theme={theme}>{children}</PaperProvider>;
}
return container(
userAuth && user && brand ? (
<Context.Provider value={{userAuth, user, brand}}>
<SignedInStack />
</Context.Provider>
) : (
<SignedOutStack />
)
);
}
export default App;
In another component, when the user signs out, all the listeners are listening again and removing itself more than once.
Logs:
This is adding unnecessary reads and writes to Firebase which increase costs.
I'm new using useEffect and I'm pretty sure that I'm doing something wrong. I don't understand completely the dependencies needed for the useEffect to start listening the user's document and the brand's document once and to remove the listeners once. I tried to read before doing this and I thought I got it right.
- Also, for some reason that I don't see, the ActivityIndicator is removed without the user's document and the brand's document. It should be visible until the UserAuth, User document and Brand document are set.
My goal here is:
- Show the ActivityIndicator until all the listeners are set.
- Listen and remove the listeners just once to avoid unnecessary reads and writes.