I have a function as follows:
recieveUserData = async() =>{
console.log(firebase.auth().currentUser.uid);
let user =firebase.auth().currentUser.uid;
await firebase.database().ref('/Users/'+user+'/Public').once('value').catch(error=>{console.log("error:" + error)}).then((snapshot)=>{
//Do something.
})
}
The console log, actually produces the uid. However, the function that requires the variable 'user' and hence 'firebase.auth().currentUser.uid' fails:
[Unhandled promise rejection: TypeError: Cannot read property 'uid' of null].
I am thinking that perhaps firebase.auth() may take some time to be called and hence the later line errors out. I thought I could solve this by changing the above to something like the following:
console.log(firebase.auth().currentUser.uid);
await firebase.auth()
.catch(err=>{console.log(err)})
.then(response=>{console.log(response)});
I was thinking I could write my later lines in the .then(), but I get a different error:
Unhandled promise rejection: TypeError: _firebase.default.auth(...).catch is not a function]
I am very confused as I use something similar in a different module and it seems to work fine. I am slightly worried that it will break now given these errors. Any insight would be really handy.
Thanks in advance
You do not need to use both sync and asynchronism at the same time.
You are not familiar with the use of 'async' and 'await'. If you understand 'sync', you can see the problem with your code. You made it a 'async' function and put 'await' in the firebase. Then the function starts from firebase, so the value of 'user' is not defined, and it generates null. This results in an error. Promise of '.then' is same the 'async' and 'await' function.
Could you try this code?
//async
const {currentUser} = await firebase.auth()
let userUid = currentUser.uid
//sync
let userUid;
firebase.auth().then( currentUser => {
userUid = currentUser.uid
})
.catch(error => console.log(error) )
In the end I managed to test whether I was authenticated in ComponentDidMount in a parent module and render the relevent modules only if I was authenticated. I also used firebase.auth().onAuthStateChanged as this would update as soon as I had been authenticated, i.e. it gave firebase enough time to return an actual currentUser and not null. This then meant my initial code/all other different solutions I have tried now actually work:
class WholeProject extends Component {
state={
user:null
}
componentDidMount = () => {
firebase.auth().onAuthStateChanged((user)=>{
if (user) {
this.setState({ user });
} else {
this.setState({ user: null });
}
});}
render(){
let authenticated;
if (this.state.user === null)
authenticated=<Text>Unauthenticated</Text>;
else authenticated=<Profile user = {this.state.user}/>;
return(
<React.Fragment>
{authenticated}
</React.Fragment>
);
}
}
export default WholeProject;
Related
I am trying to get id of an object after set that object. But I am getting type error. TypeError: Cannot read properties of undefined (reading 'val'). How should I do that with firebase 9?
Here is the code that I want to work:
set(push(ref(db, "expenses")), expense)
.then((snapshot) => {
console.log(snapshot.val());
dispatch(
addExpense({
id: snapshot.key,
...expense,
})
);
})
.catch((e) => {
console.log("This failed.", e);
});
Thanks in advance.
Why your code doesn't work
The documentation of set(ref, value) shows that is is defined as:
function set(ref: DatabaseReference, value: unknown): Promise<void>
It returns a Promise<void>, so there's no snapshot being passed to your then.
If the promise resolves (and thus your then callback gets called) that the expense was written to the database on the server as is.
How to fix it
If you want to get the key of the push call, you can capture that outside of the set call already:
const newRef = push(ref(db, "expenses"));
set(newRef, expense)
.then(() => {
dispatch(
addExpense({
id: newRef.key,
...expense,
})
);
})
.catch((e) => {
console.log("This failed.", e);
});
Calling push is a pure client-side operation, which is synchronous, so that doesn't require await or then (which should be used with asynchronous operations).
Further considerations
Note though that now you're only showing the expense locally after it's been written to the server. If that is a requirement for your use-case, then 👍. But when using Firebase it is quite common to:
Use a permanent, onValue listener on expenses to show the latest expenses in the UI.
Write the new expense with a simple call, without a then() listener: set(push(ref(db, "expenses")), expense);
The Firebase SDK will then immediately call the local onValue listener with the new value, with the assumption that the write will succeed.
So your UI will show the local value straight away, giving the user an almost instant response.
In the (more uncommon) case that the server (i.e. your security rules) rejects the write operation, the SDK calls onValue again with the corrected data, so your UI can update the state.
I'd like to implement Firestore offline persistence on my PWA React app using the reactfire library.
const firestore = useFirestore().enablePersistence();
let documentReference = firestore
.collection("food")
.doc("milkshake");
const { data } = useFirestoreDocData(documentReference);
but running the code i get an error:
FirebaseError: Firestore has already been started and persistence can no longer be enabled. You can only enable persistence before calling any other methods on a Firestore object.
This component is wrapped inside a <Suspense> as mentioned in the documentation
That database read is the only one that i make in the entire app, how can i solve?
Edit.
Using the example that #Ajordat gave, I've imported the preloadFirestore function inside the App component I do get an error:
"Cannot read property 'name' of undefined".
Whereas adapting (because I cannot use hooks inside the fetch function)
the example from #DougStevenson: I've imported useFirestore function in the App component (in order to get the Firestore object) to enable persistence, and then importing it (useFirestore) into my component in order to retrieve the data, but now, I get the same error as before,
Firestore has already been started and persistence can no longer be enabled.
Edit 2:
I've tried to enablePersistence without errors, thank guys, this is my approach, let me know if it is the best:
const firestore = useFirestore();
React.useEffect(() => {
firestore.enablePersistence();
}, []);
And in my custom component:
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let document = useFirestoreDocDataOnce(docRef);
console.log(document)
But now I do have a problem, when I log the document, the data are not emitted instantly, yeah I know that it is an asynchronous operation, but the component is wrapped inside a <Suspense>, in this way:
<Suspense fallback={<div>Loading</div>}>
<FoodComponent foodName={"Milkshake"} />
</Suspense>
But I don't see the loading text before the component is actually rendered.
Does the suspense fragment show the fallback component only while is loading the function (useFirestore) and not the actual data?
Well, I've solved, have to destructure the data, doing like that:
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let { data: document } = useFirestoreDocData(docRef);
console.log(document)
On other JavaScript libraries for Firestore, enablePersistence() returns a promise. That means it will complete some time in the future, with no guarantees how long it will take. If you're executing the query immediately after you call enablePersistence(), without waiting for the returned promise to become fulfilled, then you will see this error message. That's because the query "beats" the persistence layer and effectively executes first.
You will have to figure out how to use that promise to wait until it's OK to make that query with persistence enabled. For example:
seFirestore().enablePersistence()
.then(() => {
let documentReference = firestore
.collection("food")
.doc("milkshake");
const { data } = useFirestoreDocData(documentReference);
})
.catch(error => {
console.error("enablePersistence failed", error);
})
Notice how the query will complete only after the persistence is fully enabled.
Thanks for the suggestion guys #DougStevenson and #Ajordat
In app component:
import { useFirestore } from "reactfire"
...
const firestore = useFirestore();
React.useEffect(() => {
firestore.enablePersistence();
}, []);
In your custom component, where you want to use Firestore:
import { useFirestore, useFirestoreDocData /* or what you want to use */ } from "reactfire"
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let { data: document } = useFirestoreDocData(docRef);
console.log(document);
I have a child component MySummary that calls the function passed from the parent component like this:
submit() {
this.setState({submitting: true});
this.props.handleSubmit()
.finally(() => {
if (!this.mounted) {
return;
}
this.setState({submitting: false, showConfirmDialog: false});
});
}
And in the parent component the handleSubmit function returns a promise and redirects on submit to another page:
handleSubmit() {
return this.gateway.submitExam()
.then((exam) => {
this.setState(exam);
this.props.history.push('/exam/result');
});
}
In browser it seems to be working. And even though I thought finally would never been called because we redirect to another page, and the child component would be unmounted then, it seems that it still reaches that block of code. I have logged into console from the finally block when I was running that code. So I am not sure how is that happening. The problem I have is that my test is failing and that I get the error:
TypeError: Cannot read property 'finally' of undefined
In my test I am clicking on a button that calls the submit function, and then I am just checking if the handleSubmit function was called:
I am mounting the component like this:
< MySummary exam={exam} handleSubmit ={jest.fn()}/>
And this is the test implementation:
const submitButton = confirmDialog.findByProps({'data-testid': 'submit-exam'});
submitButton.props.onClick();
expect(mySummary.props.handleSubmit).toHaveBeenCalled();
But, that test gives the error mentions above. I assume the error comes from wrong implementation of the prop handleSubmit, which is only defined as jest.fn(). How can I mock the promise implementation so that I can get this test to work?
You need your mocked function to return a Promise, like the actual function does. So, try setting the mock's return value to a resolved Promise:
handleSubmit={jest.fn(() => Promise.resolve())}
See An Async Example and Mock Functions from the Jest docs for more info.
When I hit the button in a page to pass the search results + authentication token, I get the following error
TypeError: getState is not a function
I attached the image as it shows an exact trace of the error.
How can I solve this ?
Try this
export const searchResults = (name) =>
(dispatch, getState) => {
axios.get(`http://127.0.0.1:8000/api/?name=${name}, tokenConfig(getState))
It appears the problem is that in SearchBar.js you're calling actions.searchResults(name). Then in researcher.js you're calling tokenConfig with getState which is the second parameter, but since searchResults was only called with one argument it's undefined. So finally in auth.js invoking getState() is erroring because it's undefined.
It likely isn't the functionality you want but you could test this theory by calling actions.searchResults(name, () => ({ auth: { token: "test" } })) to verify.
Otherwise, adding an undefined check. For example token = getState && getState().auth.token would at least prevent this error but my push it elsewhere.
I want to save a user to Firebase's Realtime Database upon user creation in a sign-up form. If I return the Firebase function (value), for saving users, in a .then handler instead of just calling it (without return statement), I get an error message from React, saying "Can't perform a react state update on an unmounted component".
My code for the submit handler of the sign-up form looks something like the following:
const SignUpFormBase = (props) => {
const [credentials, setCredentials] = useState(INITIAL_STATE);
const [error, setError] = useState(null);
[some other code]
const handleSubmit = (e) => {
firebase
.doCreateUserWithEmailAndPassword(email, passwordOne)
.then(authUser => {
// create a user in the Firebase realtime database
return firebase.database().ref(`users/${authUser.user.uid}`)
.set({ username, email });
})
.then(() => {
setCredentials(INITIAL_STATE);
props.history.push(ROUTES.DASHBOARD);
})
.catch(error => {
setError(error);
});
e.preventDefault();
};
return (
[some jsx]
);
};
const SignUpForm = withRouter(SignUpFormBase);
export default SignUpForm;
The code actually works, whether you include or leave off the return statement. However, if you don't use a return statement, the warning won't show.
I just don't understand why I get the above-mentioned warning from firebase since I (seemingly) don't perform any state updates on the component after it has been unmounted.
Update:
The component actually unmounts before the setCredentials hook has the chance to update the state. This is not because of the push to history in the code above, but a Public Route I've implemented to show users only pages they are allowed to see. It uses the new useContext hook which triggers a re-render when the context value changes (the value is derived from subscribing to the firebase onAuthStateChanged method). I solved the issue by putting the setCredentials call into a useEffect hook:
useEffect(() => {
// set credentials to INITIAL_STATE before ComponentDiDUnmount Lifecycle Event
return () => {
setCredentials(INITIAL_STATE);
};
}, []);
However, I still don't get why a missing return statement (in the previous setup) lead to a vanishing react warning.
Telling from given code, I'm not sure what part lead to the warning. However, I'd like to provide some advices to help pin point it (Hard to write it clear in comment, so I just post as an answer).
firebasePromise
.then(() => {
// [1] async code
setCredentials(INITIAL_STATE);
// [2] sync code
// UNMOUNT happens after route change
props.history.push(ROUTES.DASHBOARD);
})
.catch(error => {
// [3] async code
setError(error);
});
I suspect the problem comes from the execution order of sync/async code. Two things you can try out to gain more info.
Check if [3] is invoked, use console.log maybe?
Wrap [2] in a setTimeout(() => {}) to make it async too.
My bet is on [3]. somehow some error is thrown after [2] is invoked.
Your problem is here:
setCredentials(INITIAL_STATE);
props.history.push(ROUTES.DASHBOARD);
most likely setCredentials is async function
and when it is trying to change state you already have different route because of props.history.push(ROUTES.DASHBOARD)
as the result you don't have component and you can't update the state
try:
setCredentials(INITIAL_STATE).then(
props.history.push(ROUTES.DASHBOARD);
)