EDIT: For anyone visiting this issue - read up on the difference between onRequest, and onCall functions. Understand how to call each of them in your client side code. It should help to understand what the problem was here.
I am building a personal project with firebase for the backend, and React for the frontend. I am trying to return errors correctly from cloud functions. The error shown by the client side isn't what is sent from the server. I will demonstrate the issue below.
I want the client side to display an error that says "The email address is already in use by another account". As you can see from the screenshot below, that is what is sent as the response object. However, whenever I do a console.log(err.message) in the client side, it says "invalid-argument". (See the console log in the screenshot below).
This is the code.
Cloud functions - index.ts
export const registerUser = https.onRequest((req, resp) => {
cors(req, resp, async () => {
const data = req.body.data;
const userData: IUserData = {
firstName: data.firstName,
lastName: data.lastName,
statusId: 1,
customerId: ''
}
try {
// Create auth user in firebase
const newAuthUser = await admin.auth().createUser({
email: data.email,
emailVerified: false,
password: data.password,
displayName: data.firstName ' ' data.lastName,
disabled: false
})
// Create a stripe customer
const uid = newAuthUser.uid;
const stripeCustomer = await createStripeCustomer(data, uid);
userData.customerId = stripeCustomer.id;
// Create user doc in firestore
const storeUserRef = app.firestore().doc('/users/' uid);
const storeUser = await storeUserRef.set(userData);
resp.status(200).json({
user: storeUser,
message: "User successfully registered"
}).send();
return;
} catch (err: any) {
console.log(err);
resp.status(400).send(err);
return;
}
})
})
React - SignupPage.tsx
function SignUpPage() {
const firebaseAuth = getAuth(FIREBASE_APP);
const functions = getFunctions(FIREBASE_APP);
const [auth, loading] = useAuthState(firebaseAuth);
const [regError, setRegError] = useState("")
const nav = useNavigate();
const { register, handleSubmit, formState: { errors }, } = useForm();
const [registering, setRegistering] = useState(false);
const registerUser = httpsCallable(functions, 'registerUser');
connectFunctionsEmulator(functions, "localhost", 5001);
// Check if a user login exists
useEffect(() => {
// If the auth has finished loading and the user exists, redirect to the
// user account page
if (auth && !loading) { nav('/account') }
}, [auth, loading, nav])
/**
* Form on submit
* @param data
*/
const onSubmit = (data: any) => {
setRegistering(true);
setRegError("");
const userData: IRegisterPayload = {
firstName: data.firstName,
lastName: data.lastName,
email: data.email,
password: data.password
}
registerUser(userData).then(async(res) => {
// return await signInWithEmailAndPassword(
// firebaseAuth, userData.email, userData.password
// );
console.log('REGISTER USER SUCCESS: ', res)
})
.then(res => {
console.log(res);
nav("/account");
})
.catch((err) => {
console.log('Console log err.message:', err.message);
setRegError(err.message);
}).finally(() => {
setRegistering(false);
})
};
return(
<SiteLayout>
<div className="px-3 py-5 mx-auto signup-wrapper w-100 container d-flex flex-column flex-grow-1">
<div className="row">
<div className="col-12 col-lg-6 col-xl-5 pe-lg-5">
<h2 className="h2 mb-4 text-center">Sign up</h2>
<p className="text-center">Already a member? <NavLink to={`/login`}>Login</NavLink></p>
<form onSubmit={handleSubmit(onSubmit)}
className="mb-2"
>
<div className="mb-3">
<label>First Name</label>
<input type={`text`}
className={`form-control${errors.firstName ? ' is-invalid': ''}`}
placeholder="Enter your first name"
disabled={registering}
{...register('firstName', { required: true})}
/>
{errors.firstName && <small className="text-danger d-block mt-1">First Name is required</small>}
</div>
<div className="mb-3">
<label>Last Name</label>
<input type={`text`}
className={`form-control${errors.lastName ? ' is-invalid': ''}`}
placeholder="Enter your last name"
disabled={registering}
{...register('lastName', { required: true})}
/>
{errors.lastName && <small className="text-danger d-block mt-1">Last Name is required</small>}
</div>
<div className="mb-3">
<label>Email</label>
<input type={`email`}
className={`form-control${errors.email ? ' is-invalid': ''}`}
placeholder="Enter your email address"
disabled={registering}
{...register('email', { required: true})}
/>
{errors.email && <small className="text-danger d-block mt-1">Email is required</small>}
</div>
<div className="mb-4">
<label>Password</label>
<input type={`password`}
className={`form-control${errors.password ? ' is-invalid': ''}`}
placeholder="Enter your password"
disabled={registering}
{...register('password',{
required: "You must specify a password",
validate: (val: string) => {
// Check the entered value meets the regex requirement
const isStrong = PASSWORD_REGEX.test(val);
return (val && isStrong) || "Password does not meet criteria";
}
})}
/>
{errors.password && <p className="small text-danger d-block my-1">{errors.password.message}</p>}
<small className="text-muted">Passwords must be a minimum of 8 characters and include:</small>
<ul className="small text-muted">
<li>At least 1 uppercase letter</li>
<li>At least 1 lowercase letter</li>
<li>At least 1 special character</li>
<li>At least 1 number</li>
</ul>
</div>
<Button isLoading={registering}
isSubmit={true}
loadingLabel="Registering..."
className="w-100"
>
Sign up
</Button>
{regError &&
<Message severity="error" text={regError} className="my-3 w-100" />
}
</form>
<p className="small text-muted">
By signing up, you confirm that you accept the Terms of Service and Privacy Policy. This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
</p>
</div>
</div>
</div>
</SiteLayout>
);
}
export default SignUpPage;
Can someone please tell me how to display the error message that I want?
CodePudding user response:
In your client app, you are saying that regusterUser is a "callable" type function, but it is not deployed that way in your backend.
// here you are saying that the backend is a "callable" type function
const registerUser = httpsCallable(functions, 'registerUser');
However, your backend is using an normal HTTP onRequest type function instead.
// here are you deploying a normal HTTP function (not "callable")
export const registerUser = https.onRequest((req, resp) => {
This won't work - you can't call HTTP type functions as a callable.
If you want a callable type function in your backend, follow the instructions in the documentation to use an onCall type function instead. From that linked documentation, note:
It's important to keep in mind that HTTPS callable functions are similar but not identical to HTTP functions. To use HTTPS callable functions you must use the client SDK for your platform together with the functions.https backend API (or implement the protocol).