This blog post originally posted on my blog site and you can find it here.
From the last post, we stop at finishing forgotPassword
mutation. Now we come to GraphQL
playground and execute the forget-password mutation.
mutation {
forgotPassword(email:"rasika@rasikag.com")
}
Then in the console
you will see the forget password URL and click it. It will show the test email. Click on the link and it will navigate to our web app. At this point, we don’t have forgot password page. Let’s go and create it. But in the link, you will see the token that we created.
Create a folder called change-password
as the top level and the file will be [token].tsx
. This is the Next.js
convention that we can access a variable in the URL.
Here is our page initial code block.
import { NextPage } from "next";
import React from "react";
const ChangePassword: NextPage<{ token: string }> = ({ token }) => {
return <div></div>;
};
We are using Next.js
‘s NextPage
type that leverage the functionalities that get the query parameters. To do that we are adding getInitialProps
method. Add below code block after the ChangePassword
. This method will catch the query parameter and pass it as token
ChangePassword.getInitialProps = ({query}) => {
return {
token: query.token as string
}
}
Now let’s create the forget password form. We can grab that from the login page.
return (
<Wrapper variant="small">
<Formik
initialValues={{ newPassword: "" }}
onSubmit={async (values, { setErrors }) => {}}
>
{({ isSubmitting }) => (
<Form>
<Box mt={4}>
<InputField
name="newPassword"
placeholder="new password"
label="New Password"
type="password"
/>
</Box>
<Button
isLoading={isSubmitting}
mt={4}
type="submit"
colorScheme="teal"
>
Change Password
</Button>
</Form>
)}
</Formik>
</Wrapper>
);
You will see that our onSubmit function doesn’t have any code. Before that in here, we are receiving the token and we need to send the new password to the server to reset it. Let’s add that mutation UserResolver
.
@Mutation(() => UserResponse)
async changePassword(
@Arg("token") token: string,
@Arg("newPassword") newPassword: string,
@Ctx() { em, redis, req }: RedditDbContext
): Promise<UserResponse> {
// first validate the password
if (newPassword.length <= 2) {
return {
errors: [
{
field: "newPassword",
message: "length must be greater than 2",
},
],
};
}
// check user id exist
const userId = await redis.get(FORGET_PASSWORD_PREFIX + token);
if (!userId) {
return {
errors: [
{
field: "token",
message: "token expired",
},
],
};
}
const user = await em.findOne(User, { id: parseInt(userId) });
if (!user) {
return {
errors: [
{
field: "token",
message: "user no longer exist",
},
],
};
}
user.password = await argon2.hash(newPassword);
await em.persistAndFlush(user);
req.session.userId = user.id;
return { user };
}
We are validating the password first. Then validate the userid
by checking the token. If all validation pass, then update the user. We need to hash this password first. Then update the user. Also here we are setting the session for that user.
We complete the back-end. Let’s go and add the front-end code for this change. We are starting by adding new graphql
mutation. Create a file changePassword.graphql
and add the below code.
mutation ChangePassword($token: String!, $newPassword: String!) {
changePassword(token: $token, newPassword: $newPassword) {
errors {
...RegularError
}
user {
...RegularUser
}
}
}
To manage errors we can create we created RegularError
fragment and we can replace all the errors
with RegularError fragment.
fragment RegularError on FieldError {
id
username
}
At this point, if we check our login
, register
and changePassword
mutations we can see that body is the same. So let’s make another fragment and replace it. Create a fragment called RegularUserResponse
and replace others with it.
fragment RegularUserResponse on UserResponse {
errors {
...RegularError
}
user {
...RegularUser
}
}
Main thing need to observe that fragments are base on back-end
ObjectType
. If you check thatUserResponse
is match with back-endUserResponse
ObjectType
and those names should be match.
Now let’s replace those graphql
queries with this fragment.
Make sure to run
yarn gen
from web app after doing all the things to generate the types.
Now we are going to file the onSublit
method in ChangePassword
component.
// add below code lines above from return method
const router = useRouter();
const [, changePassword] = useChangePasswordMutation();
// ...
onSubmit={async (values, { setErrors }) => {
const response = await changePassword({
newPassword: values.newPassword,
token,
});
if (response.data?.changePassword.errors) {
// the graphql errors like this
// [{filed: "username", message: "value empty"}]
setErrors(toErrorMap(response.data.changePassword.errors));
} else if (response.data?.changePassword.user) {
// TODO: try to move this else if block
// user log in successfully
router.push("/");
}
}}
But there are few things that need to handle. One thing is we can get the response that saying the error field is token and in the form we don't have any field called the token. From the next post, we are going to handle that.
Thanks for reading this. If you have anything to ask regarding this please leave a comment here. Also, I wrote this according to my understanding. So if any point is wrong, don’t hesitate to correct me. I really appreciate you.
That’s for today friends. See you soon. Thank you.
References:
This article series based on the Ben Award - Fullstack React GraphQL TypeScript Tutorial. This is amazing tutorial and I highly recommend you to check that out.
Main image credit
Top comments (0)