I am using Firebase's Web SDK in my Expo project, Redux is included too.
firebase.auth().signInWithEmailAndPassword() returns a Promise; I used .then() to handle the Promise and .catch() for any errors. This action is done in a separate file, not in the Login Screen because I dispatch an action in the then() clause.
Question is how would I be able to catch the error at the client's side? I can't seem to find any scenario that is similar to mine.
It's a school project :') Appreciate any help please!
LoginScreen:
I have tried calling the Toast from a useEffect and in the catch (error) { ... } clause too but to no avail. I know this way would not work but I cant seem to find any solution online.
const [loading, setLoading] = React.useState(false);
const [showAlert, setShowAlert] = React.useState(false);
const [alertMessage, setAlertMessage] = React.useState('');
const [alertStatus, setAlertStatus] = React.useState('');
const [error, setError] = React.useState(null);
const dispatch = useDispatch();
React.useEffect(() => {
if (error) {
// setAlertMessage(error);
// setShowAlert(true);
// setAlertStatus('danger');
Alert.alert('Error Occured', error, [{ text: 'Close' }]);
}
}, [error]);
const logInHandler = async () => {
try {
if (email.length === 0 || password.length === 0) {
setAlertMessage('You have empty fields!');
setShowAlert(true);
setAlertStatus('warning');
return;
}
dispatch(authActions.logIn(email, password));
setError(null);
setLoading(true);
setAlertMessage('Logging In...');
setShowAlert(true);
setAlertStatus('info');
} catch (err) {
// setAlertMessage(err.message);
// setShowAlert(true);
// setAlertStatus('danger');
setError(err.message);
setLoading(false);
}
};
return ( // i still don't get how to format properly when i paste codes here, sorry
{showAlert && (
<Toast // this is a custom toast in a separate component that i made that only shows when there is an error
message={alertMessage}
status={alertStatus}
hide={show => setShowAlert(show)}
/>
)}
);
File containing the firebase auth methods:
Throwing the error causes an Exception, but what i'm trying to achieve is to get the error message and display it using the custom Toast if there is an error from firebase
export const signUp = (email, password, name, gender) => dispatch => {
firebase
.auth()
.createUserWithEmailAndPassword(email.trim().toLowerCase(), password)
.then(res => {
firebase
.auth()
.currentUser.updateProfile({ displayName: name })
.then(() => {
dispatch({
type: STORE_USER_DATA,
id: res.user.uid,
user: res.user.displayName
});
dispatch({
type: SET_REGISTER,
isRegistering: true,
gender: gender
});
})
.catch(err => console.log('err.message', err.message));
})
.catch(err => {
let message = 'actions.signUp: An error has occured!';
let hasError =
err.code === 'auth/email-already-in-use' ||
err.code === 'auth/invalid-email' ||
err.code === 'auth/weak-password';
if (hasError) {
message = 'Invalid Credentials!';
}
throw new Error(message);
});
};
you probably need to dispatch the error instead of throwing the error - because the main usage of redux is state management so rather than throwing the error it makes more sense that you have a state that indicates if the login has failed or succeeded.
However i think it might be worth it to look at react-redux-firebase package its a clean integration between firebase and react/redux application - you could checkout authReducers, and authErrorReducers i think they might be helpful
Related
Hopefully a simply one.
I make an API call in my component which brings down some account information such as AccountUid, Category etc, i use state to set these.
useEffect(() => {
fetch(feed_url, {
headers: {
//Headers for avoiding CORS Error and Auth Token in a secure payload
"Access-Control-Allow-Origin": "*",
Authorization: process.env.REACT_APP_AUTH_TOKEN,
},
})
//Return JSON if the Response is recieved
.then((response) => {
if (response.ok) {
return response.json();
}
throw response;
})
//Set the Account Name state to the JSON data recieved
.then((accountDetails) => {
setAccountDetails(accountDetails);
console.log(accountDetails.accounts[0].accountUid);
console.log(accountDetails.accounts[0].defaultCategory);
})
//Log and Error Message if there is an issue in the Request
.catch((error) => {
console.error("Error fetching Transaction data: ", error);
});
}, [feed_url]);
This Works perfectly well and it Logs the correct values in my .then when testing it.
The issue however is that i want to pass these down as props. But i get an error that they are being returned as null (My default state).. i presume as they're jumping ahead.
<div className="App">
<GetAccountName
accountUID={accountDetails.accounts[0].accountUID}
defCategory={accountDetails.accounts[0].defaultCategory}
/>
</div>
How do i pass the the 2 details im logging as props?? I've tried setting default state to "" instead of null and just get that it is undefined.
If you dont want to use conditional render in your child component, so you should try optional chaining
<GetAccountName
accountUID={accountDetails?.accounts?.[0]?.accountUID}
defCategory={accountDetails?.accounts?.[0]?.defaultCategory}
/>
Since fetching is asyncronous, the most common way is to show some loading indicator (like a spinner) & once the data come in, show the component instead.
If you don't need an indicator, you might just return null.
The general idea is to manipulate some intermediary states (e.g. data, isError) based on the promise state.
Check out react-query library example or a lighter abstraction like useFetch hook to see how they manage it.
Here's a sample implementation of useFetch taken from this article:
const useFetch = (url, options) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
const [abort, setAbort] = React.useState(() => {});
React.useEffect(() => {
const fetchData = async () => {
try {
const abortController = new AbortController();
const signal = abortController.signal;
setAbort(abortController.abort);
const res = await fetch(url, {...options, signal});
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
return () => {
abort();
}
}, []);
return { response, error, abort };
};
I'm using React Native/Firebase/Redux to build a simple login system. I am trying to work out how to capture errors that happen as a result of failed login attempts.
Here's my authscreen.js:
const [alertShowing, setAlertShowing] = useState(false);
const [alertMessage, setAlertMessage] = useState('');
...
function handleLogin() {
const response = dispatch(login(email, password));
console.log(response);
}
actions.js:
export const login = (email, password) => {
return async (dispatch) => {
try {
const response = await Firebase.auth().signInWithEmailAndPassword(email, password);
dispatch(getUser(response.user.uid));
} catch (e) {
return e;
}
};
};
My console.log(response) above correctly shows me the error message, but this obviously isn't very useful to users. And please note too that I can log in properly when using correct credentials.
What I really want to do in my handleLogin() is check if the response is an error, and if so, setlAlertShowing(true) and setAlertMessage to what I've gotten back from the useDispatch hook so that I may display it nicely to the user.
How should I go about this? TIA.
Firebase errors messages are designed for developers and not standard users friendly. The solution is to identify authentication error code and map with user-friendly messages.
list of error code https://firebase.google.com/docs/auth/admin/errors
You can use function like this to map authError to meaningful error messages.
function mapAuthCodeToMessage(authCode) {
switch (authCode) {
case "auth/invalid-password":
return "Password provided is not corrected";
case "auth/invalid-email":
return "Email provided is invalid";
// Many more authCode mapping here...
default:
return "";
}
}
and use it later
export const login = (email, password) => {
return async (dispatch) => {
try {
const response = await Firebase.auth().signInWithEmailAndPassword(email, password);
dispatch(getUser(response.user.uid));
} catch (e) {
dispatch({type:"authError",message:mapAuthCodeToMessage(e.code)}));
}
};
};
I'm using React, and just wanted some advice on error handling.
I have my fetch request in an async function, this function is in another folder and is being imported in my App.js file. Im doing this because I want to try out testing with mock service worker, and have read its easier with the requests in a separate file.
From looking at my code below, is this best practice for error handling? Is there a better way thats more concise?
Here is my fetch async function, at the moment i've purposely gave the wrong env variable name so it will give me a 401 unauthorised error.
require('dotenv').config()
export const collect = async () => {
const key = process.env.REACT_APP_API_KE
try{
const res = await fetch(`http://api.openweathermap.org/data/2.5/weather?q=london&appid=${key}`)
if(res.status !== 200){
throw new Error(res.status)
}
const data = await res.json()
return data
} catch (error){
let err = {
error: true,
status: error.message,
}
return err
}
}
This is being called in my App.js file (not rendering much at the moment)
import { useState } from 'react'
import { collect } from './utilities/collect'
require('dotenv').config()
function App() {
const [data, setData] = useState("")
const [error, setError] = useState({ error: false, status: "" })
const handleFetch = async () => {
let newData = await collect()
if(newData.error){
setError({ error: newData.error, status: newData.status })
}else {
setData(newData)
}
}
return (
<div className="App">
<h1>weather</h1>
<button onClick={handleFetch}>fetch</button>
</div>
);
}
export default App;
Any help or advise would be great.
When writing an abstraction around Promises or async and await one should make sure it is used appropriately, that is a good Promse must allow it consumer to use it then and catch method or should allow it consumer use try and catch to consume it and provide appropriate information on Errors
From Your code, The abstraction doesnt gives back an appropriate response and doesnt follow the standard behavior of a promise it always resolve and never reject and though the code works its implementation of the collect is different from a standard Promise and wont be nice for a standard code base, for example a good abstraction will provide error information returned from the third party api
Appropriate way to amend code
The third party api returns this response
{
"cod":401,
"message": "Invalid API key. Please see http://openweathermap.org/faq#error401 for more info."}
This should be your implementation
// Your fetch functon abstraction modified
require('dotenv').config()
const collect = async () => {
const key = process.env.REACT_APP_API_KE;
const res = await fetch(
`http://api.openweathermap.org/data/2.5/weather?q=london&appid=${key}`,
);
if (res.status !== 200) {
const error = await res.json();
throw {message: error.message,status:error.cod};
}
const data = await res.json();
return data;
};
Your app component should now be like this
import { useState } from 'react'
import { collect } from './utilities/collect'
require('dotenv').config()
function App() {
const [data, setData] = useState("")
const [error, setError] = useState({ error: false, status: "" })
const handleFetch = async () => {
try {
let newData = await collect()
setData(newData)
} catch(e){
setError({ error: e.message, status: e.status })
}
}
return (
<div className="App">
<h1>weather</h1>
<button onClick={handleFetch}>fetch</button>
</div>
);
}
export default App;
I was following this tutorial https://www.sitepoint.com/reddit-clone-react-firebase/.
Earlier it was really simple just call onSnapshot after the document is fetched, but here it is a separate function, and now here comes the issue, when I try to call that onSnapshot by passing document, but it always says,no matter what type of data I tried to pass it as the first param, it always says, it is not type of 'bc' which it expects.
FirebaseError: Expected type 'bc', but it was: a custom he object
useEffect(async () => {
const postsCollection = collection(db, "posts");
const orderedCollection = query(
postsCollection,
orderBy("createdAt", "desc")
);
try {
onSnapshot(
orderedCollection, // here I think the problem is!
(querySnapshot) => {
console.log("yahaan se problem hai");
console.log(querySnapshot);
const _posts = [];
querySnapshot.forEach((doc) => {
console.log(doc);
_posts.push({
id: doc.id,
...doc.data(),
});
});
console.log(_posts);
// setPosts(_posts);
},
(error) => {
console.log("error occured: ", error);
},
() => {
console.log("completed");
}
);
} catch (e) {
console.log("ye kya drama hai:", e);
} finally {
console.log("finally");
}
}, []);
Okey, so I had the same problem and I found a solution after struggling with the newest version of firebase for a while.
I don't know if you're using a class component or a functional one, in this example i'm using a funcional component but I assume it'll work the same if you replace the react hooks.
import { getFirestore, collection } from 'firebase/firestore'
const db = getFirestore();
const colRef = collection(db, "team")
const [results, setResults] = useState([]);
useEffect(() => {
let isMounted = true;
onSnapshot(colRef, (snapshot) => {
if (isMounted) {
const results= snapshot.docs.map((doc) => {return {...doc.data(), id: doc.id}});
setResults(results)
}
});
return () => { isMounted = false };
}, []);
This way your component'll listen to updates everytime the data changes, after that you can personalize it using querys but i wanted to show you a simple example so it's easy to understand.
I had the same problem, unfortunately, the above didn't help me. in my case I was actually importing form functions and types from '#firebase/firestore' and others from 'firebase/firestore'... this was done by autoImport. the moment I made all of them get the types and functions from the same place it worked instantly
These kind of errors occur usually when the functions aren't used the way they're supposed to. I can't really tell where the problem comes from in your code but you may try the getDocs method instead and a state variable to store your values
try this code.
const [Results, setResults] = useState([]);
useEffect(() => {
const FetchedPosts = async () => {
const querySnapshot = await getDocs(
collection(db, 'posts'),
orderBy("createdAt", "desc")
);
querySnapshot.forEach((doc) => {
setResults((prevState) => [...prevState, doc.data()]);
});
};
FetchedPosts();
}, []);
I am using Firebase auth to handle authentication of users in a form but am getting this error
error of 'Persistence' of undefined
when adding firebase.auth().setPersistence.
onSubmit = event => {
const { firebase } = this.props;
const { email, password, remember } = this.state;
firebase
.doSignInWithEmailAndPassword(email, password, remember)
.then(authUser => {
firebase.doEmailUpdateUser(authUser);
})
.then(() => {
this.setState({ error: null });
navigate(ROUTES.INDEX);
})
.catch(error => {
this.setState({
error,
errorOpen: true
});
});
event.preventDefault();
};
Whereas firebase.doSignInWithEmailAndPassword(email, password, remember) is
doSignInWithEmailAndPassword = (email, password, remember) => {
this.auth
.setPersistence(
`${
remember
? this.auth.Auth.Persistence.LOCAL
: this.auth.Auth.Persistence.SESSION
}`
)
.then(() => this.auth.signInWithEmailAndPassword(email, password));
};
for reference this.auth = app.auth();
Have I missed something here? What have I done wrong?
Looks like you're trying to get Auth.Persistence from the wrong object. You say this.auth = app.auth(), so i assume then that app was imported from firebase something like import app from 'firebase'. If that's incorrect, please show me how you're importing.
If my assumptions were correct, then you'll need to access the properties like app.auth.Auth.Persistence.LOCAL. Your current version is more like app.auth().Auth.Persistence.LOCAL