DEV Community

Cover image for Creating a Reddit Clone Using React and GraphQL - 11
Rasika Gayan Gunarathna
Rasika Gayan Gunarathna

Posted on • Updated on • Originally published at rasikag.com

Creating a Reddit Clone Using React and GraphQL - 11

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")
}

Enter fullscreen mode Exit fullscreen mode

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>;
};
Enter fullscreen mode Exit fullscreen mode

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
  }
}

Enter fullscreen mode Exit fullscreen mode

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>
);
Enter fullscreen mode Exit fullscreen mode

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 };
  }

Enter fullscreen mode Exit fullscreen mode

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
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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
  }
}

Enter fullscreen mode Exit fullscreen mode

Main thing need to observe that fragments are base on back-end ObjectType . If you check that UserResponse is match with back-end UserResponse 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("/");
  }
}}

Enter fullscreen mode Exit fullscreen mode

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

Discussion (0)