Firestore onSnapshot does not get initial values when app launches - firebase

I'm building a mobile app using React Native and Redux and Firestore. I'm trying to fetch data in my componentDidMount method, but also listen for updates on that data. So I decided to use the onSnapshot method to listen to a users document in my database. So my redux action looks like this:
export const onUserDetailsChange = () => (dispatch) => {
const uid = auth().currentUser.uid;
return firestore()
.collection('users')
.doc(uid)
.onSnapshot(
(documentSnapshot) => {
dispatch({
type: RECEIVED_USER_DETAILS,
payload: documentSnapshot.data(),
});
},
(err) => {
console.log(err);
},
);
};
And my componentDidMount looks like this:
componentDidMount() {
this.userDetailsListener = this.props.onUserDetailsChange();
}
When I launch on the app using npx react-native run-ios, I verified that componentDidMount does get called. And it goes to the action and it even returns an unsubscriber from the onSnapshot. But my store never updates, and even console logs within the onSnapshot don't get called either. This is only on the initial launch. When I refresh the app, then everything works normally.
Is this a firestore issue? Is there something wrong with my onSnapshot? Or is it some sort of npm cache or DerivedData issue?
I've tried searching for this issue but haven't found anyone else who has had trouble getting the initial data fetch from onSnapshot.

Related

Next.js with Firebase Remote Config

I was trying to integrate Google's Firebase Remote config into my Next.js app.
When following Firebase's docs, and just inserted the functions directly into my component's code block, like so:
const remoteConfig = getRemoteConfig(app);
I keep getting the following error when following their documentation:
FirebaseError: Remote Config: Undefined window object. This SDK only supports usage in a browser environment.
I understand that it happens since Nextjs is rendered server-side, so there's no window object yet, so here's my solution:
import {
fetchAndActivate,
getRemoteConfig,
getString,
} from 'firebase/remote-config';
const Home: NextPage<Props> = (props) => {
const [title, setTitle] = useState<string | null>('Is It True?');
useEffect(() => {
if (typeof window !== 'undefined') {
const remoteConfig = getRemoteConfig(app);
remoteConfig.settings.minimumFetchIntervalMillis = 3600000;
fetchAndActivate(remoteConfig)
.then(() => {
const titleData = getString(remoteConfig, 'trueOrFalse');
setTitle(titleData);
})
.catch((err) => {
console.log(err);
});
}
});
return <h1>{title}</h1>}
Basically, the important part is the if statement that checks if the window object exists, then it execute the Remote Config functions according to Firebase documents.
Also, it worked outside a useEffect, but I think that's probably a bad idea to leave it outside, maybe even it should have a dependency, can't think of one at the moment.

Why is Firestore rejecting the operation?

I have a firestore action in a react-native project:
export const getMessages = (convoId) => {
return (dispatch) => {
firebase
.firestore()
.collection('messages')
.where('userConvos', 'array-contains', convoId)
.orderBy('time')
.onSnapshot((querySnapshot) => {
const messages = [];
querySnapshot.forEach((doc) => {
messages.push(doc.data());
});
dispatch({ type: types.LOAD_MSGS, payload: messages });
});
};
};
the two composite indices I've tried in firestore:
and my database structure is like so:
Messages (collection)
content (field)
time (field)
userConvos (field)
The problem is when I make this firestore call, I get the
Error: Firestore: Operation was rejected because the system is not in a state required for the opereation's execution
But if the error is dismissed, the data is ordered as expected. What's going on and how can this error be eliminated?
You have a missing index. To create it, do the following.
View the android device logs by typing on the terminal:
adb logcat
If you reproduce the error, then something like the following will appear in the logs:
05-16 04:23:20.887 3746 3895 I ReactNativeJS: nativeErrorMessage:
'FAILED_PRECONDITION: The query requires an index. You can create it
here:
https://console.firebase.google.com/v1/r/project/XXXXXXXXX/firestore/indexes?create_composite=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
Open a browser and paste the link.
Press the Create Index button.

How to limit data returned by Firestore's onSnapshot listener?

In the context of react-native chat application project, I'm using
firebase.firestore().collection("conversations").where("members", "array-contains", {email}).onSnapshot( ... ).
This listener is located in my inbox's componentDidMount()
Every time the app launches, the entire result set of the query is returned, even if there is nothing new. How can I limit the results of this listener to only what has changed from the last time it read from firestore? I've looked into redux-persist, but I'm not sure how that would limit the result set of onSnapshot.
The main goal is to minimize a potentially extreme Firebase bill. if it cant be done with onSnapshot, what are some other options that maintain realtime functionality? I'm aware the realtime database charges for storage instead of reads/writes/deletes
Migrating to firebase's realtime database is unnecessary. in worst case scenario (screens with large amounts of data), major firestore-read-savings can be had with the combination of redux-persist and this firestore query:
export const inboxLoadTest = (email, startAfter) => {
return dispatch => {
dispatch({ type: types.INBOX_LOADING });
firebase
.firestore()
.collection("conversations")
.where("members", "array-contains", email)
.orderBy("lastMsgTime")
.startAfter([startAfter])
.onSnapshot(
querySnapshot => {
let conversations = [];
querySnapshot.forEach(queryDocumentSnapshot => {
const membersArr = queryDocumentSnapshot.get("members");
let conversation = {
id: queryDocumentSnapshot.id,
conversant:
membersArr[0] === email ? membersArr[1] : membersArr[0],
lastMsgTime: queryDocumentSnapshot.get("lastMsgTime")
};
conversations.push(conversation);
});
dispatch(loadInboxSuccess(conversations));
},
errorObject => {
dispatch(loadInboxError(errorObject.message));
}
);
};
};
Once data is loaded, redux-persist will save to asyncstorage so on app re-load, the above listener will only fire for messages received after the last message stored in redux/asyncstorage, instead of the entire collection of messages, saving on the result set, and thus the firebase billing

How to know if Firebase Auth is currently retrieving user?

Background
I am using GoogleAuthProvider, with the default LOCAL persistence.
When I navigate to the page, I do:
firebase.initializeApp(firebaseConfig)
firebase.auth().currentUser // this is always null
firebase.auth().onAuthStateChanged(user => {
console.log("authStateChanged", user)
})
If the user is logged in, the callback is called once, with the user.
If the user is not logged in, the callback is also called once, with null.
This suggests I could wait until the first callback after navigating to the page to get the real login state before deciding what view to display, for instance. (I originally thought that it would not get called with null, and so I could end up waiting indefinitely)
Question
Would that be idiomatic usage? Does it seem like it will be robust against updates to firebase? Where can I find this discussed in the official documentation?
2022 Edit: in firebase web SDK 9, it's
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
export const isReady = new Promise(resolve => {
const unsubscribe = onAuthStateChanged(auth, (/* user */) => {
resolve(/* user */)
unsubscribe()
})
})
P.S: The reason I don't resolve with the user is because it is available at auth.currentUser, while the promise would retain an outdated value.
Looking at similar questions such as Pattern for Firebase onAuthStateChanged and Navigation Guards - Quasar app it seems this is indeed the way it's done.
So I have come up with the following to differentiate the initial condition:
export const isReady = new Promise(resolve => {
const unsubscribe = firebase.auth().onAuthStateChanged(() => {
resolve()
unsubscribe()
})
})
I export this Promise from the module where I wrap firebase, so I can begin other initialization while waiting for an authoritative authentication state.
this worked for me instead. NB: For those user Quasar
export default async ({ app, router, store }) => {
return new Promise(resolve => {
const unsubscribe = auth.onAuthStateChanged((user) => {
auth.authUser = user
resolve()
unsubscribe()
})
})
}

Firebase sdk with react native unable to see anything

I am following this tutorial on RN with Firestore. I've so far only used the Firebase Web SDK installed via
npm install firebase -save
With the following example code:
constructor(props) {
super(props);
this.ref = firebase.firestore().collection('sessions');
this.unsubscribe = null;
this.state = {
dataSource: [],
loading: true,
};
}
componentDidMount() {
this.unsubscribe = this.ref.onSnapshot(this.onCollectionUpdate);
}
componentWillUnmount() {
his.unsubscribe();
}
onCollectionUpdate = (querySnapshot) => {
const dataSource = [];
querySnapshot.forEach((doc) => {
const { id, desc, zipcode, timestamp } = doc.data();
dataSource.push({
key: doc.id,
doc,
desc,
zipcode,
timestamp,
});
});
this.setState({
dataSource,
loading: false,
});
}
The above code returns absolutely nothing, even if I put a bogus collection name. Nothing runs, and I put a bunch of console.log statements but still can't see anything. I can't even tell if there is any problems connecting to Firestore.
I have not yet tried react-native-firebase module because I thought I am only doing a simple Firestore query, but at same time I am building my app natively on iOS on a Mac.
Am I supposed to be using the react-native-firebase module?
There is a typo error in your componentWillMount. componentWillUnmount() {
his.unsubscribe();
}
should be: componentWillUnmount() {
this.unsubscribe();
}
Also i recommend react-native-firebase.
So for anyone who found this with similar issues, just want to confirm that the Firebase Web SDK is indeed possible to be used for Firestore. There is no need to use react-native-firebase if your use case is very simple CRUD.
My error was that it was pulling the database content just that my render function was not displaying it.

Resources