DEV Community

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

Posted on • Originally published at rasikag.com

Creating a Reddit Clone Using React and GraphQL - 14

This blog post originally posted on my blog site and you can find it here.

Let’s create a folder called middleware and add the isAuth.ts file and blow code to it.

import { RedditDbContext } from "../types";
import { MiddlewareFn } from "type-graphql";

export const isAuth: MiddlewareFn<RedditDbContext> = ({ context }, next) => {
  if (!context.req.session.userId) {
    throw new Error("not authenticated");
  }
  return next();
};
Enter fullscreen mode Exit fullscreen mode

Then we can use this middleware in our post resolver. This @UseMiddleware is coming from type-graphql


@Mutation(() => Post)
@UseMiddleware(isAuth)
async createPost(
// ... remaining code

Enter fullscreen mode Exit fullscreen mode

Now we are moving to the front-end app and going to add Post form. Let’s create create-post.tsx file in pages folder.

Let’s add this code to it.

const CreatePost: React.FC<{}> = ({}) => {
  return (
    <Wrapper variant="small">
      <Formik
        initialValues={{ title: "", text: "" }}
        onSubmit={async (values) => {}}
      >
        {({ isSubmitting }) => (
          <Form>
            <InputField name="title" placeholder="title" label="Title" />
            <Box mt={4}>
              <InputField name="text" placeholder="text..." label="Body" />
            </Box>
            <Button
              isLoading={isSubmitting}
              mt={4}
              type="submit"
              colorScheme="teal"
            >
              Create Post
            </Button>
          </Form>
        )}
      </Formik>
    </Wrapper>
  );
};

export default CreatePost;
Enter fullscreen mode Exit fullscreen mode

It is better to have a textarea for body field. We can change the InputField component. We can accept a prop called as textarea and define it as boolean.

... // InputFieldProps
textarea?: boolean

Enter fullscreen mode Exit fullscreen mode

Then check it in the InputField component.


let InputOrTextarea = Input
if (textarea) {
  InputOrTextarea = Textarea
}
...
<InputOrTextarea
{...props}
{...field}
id={field.name}
placeholder={props.placeholder}
/>

Enter fullscreen mode Exit fullscreen mode

Now we need to add the mutation. Create a file called createPost.graphql and add the below code. Then run yarn gen


mutation CreatePost($input: PostInput!) {
  createPost(input: $input){
    title
    id
    createdAt
    creatorId
    updatedAt
    text
    points
  }
}

Enter fullscreen mode Exit fullscreen mode

Then we can use useCreatePostMutation and process the request to create a post. Also, in this create post page, we need to add the NavBar to this page. Let’s create a common component for it. Create a component called Layout.tsx and add this code block.

import { NavBar } from "./NavBar";
import { Wrapper, WrapperVariant } from "./Wrapper";

interface LayoutProps {
  // added WrapperVariant type
  variant?: WrapperVariant;
}

export const Layout: React.FC<LayoutProps> = ({ children, variant }) => {
  return (
    <>
      <NavBar />
      <Wrapper variant={variant}>{children}</Wrapper>
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

Because we used Wrapper inside here. So we need to set the variant in the Layout. Without duplicating code we can create a type in there.

// Wrapper.tsx
export type WrapperVariant = "small" | "regular";
// change inside the Wrapper.tsx
interface WrapperProps {
  variant?: WrapperVariant;
}
Enter fullscreen mode Exit fullscreen mode

Then replace it all over the places that we used variant. Then we can replace the Wrapper with Layout in the create-post.tsx page.

<Layout variant="small">...</Layout>
Enter fullscreen mode Exit fullscreen mode

Now we can make this navbar sticky. Add the below code to make it sticky.

// NavBar.tsx
<Flex zIndex={1} position="sticky" top={0} bg="tomato" p={4}>
...

Enter fullscreen mode Exit fullscreen mode

Now at this point, once a user creates a post without login into the system, this will throw an unauthenticated error. We can simply manage it on onSubmit function.


// create-post.tsx
onSubmit={async (values) => {
  const { error } = await creatPost({ input: values });

  if (error?.message.includes("not authenticated")) {

   router.push("/login")
  } else {
   router.push("/");
  }
}}

Enter fullscreen mode Exit fullscreen mode

The issue in this approach we need to check in every graphql query that, the user is authenticated or not. We can create a global error handler for this. Add below code to createUrqlClient.ts and add this exchanges.

import { pipe, tap } from "wonka";
const errorExchange: Exchange = ({ forward }) => (ops$) => {
  return pipe(
    forward(ops$),
    tap(({ error }) => {
      if (error?.message.includes("not authenticated")) {
        Router.replace("/login");
      }
    })
  );
};
// ...
// below in this file
exchanges: [
  // ...
  errorExchange,
  ssrExchange,
  fetchExchange,
];
Enter fullscreen mode Exit fullscreen mode

Once a user tries to do something without login, this will navigate to the login page.

We can make this better by checking the user is logged in by checking me query. To do that we can create a hook. Inside the utils folder add a file called useIsAuth.ts and add the below code.

import { useRouter } from "next/router";
import { useEffect } from "react";
import { useMeQuery } from "../generated/graphql";

export const useIsAuth = () => {
  const [{ data, fetching }] = useMeQuery();
  const router = useRouter();
  useEffect(() => {
    if (!fetching && !data?.me) {
      router.replace("/login?next=" + router.pathname);
    }
  }, [fetching, data, router]);
};
Enter fullscreen mode Exit fullscreen mode

In here we are adding router.pathname as query parameter. This will route back to the previous page that the user, where were before. Let’s user this in create-post.tsx page.

const CreatePost: React.FC<{}> = ({}) => {
const router = useRouter();
useIsAuth();
// ...

Enter fullscreen mode Exit fullscreen mode

Then in the login page when there is a success login with next query parameter navigate to that page.

// ...
// inside the onSubmit method
else if (response.data?.login.user) {
  if (typeof router.query.next === "string") {
    router.push(router.query.next);
  } else {
    router.push("/");
  }
}

Enter fullscreen mode Exit fullscreen mode

If there is no error, navigate to the home page.

// inside the onSubmit method
const { error } = await creatPost({ input: values });
if (!error) {
  router.push("/");
}
Enter fullscreen mode Exit fullscreen mode

Also, we can modify the token-passing mechanism in the [token].tsx page, because the token also can access as a query value. So, let’s change that code also.


const ChangePassword: NextPage<{ token: string }> = () => {
...
// previously we took token as initial prop.
// const ChangePassword: NextPage<{ token: string }> = ({ token })
// ...
// get the token from the query parameter
const response = await changePassword({
newPassword: values.newPassword,
token:
typeof router.query.token === "string" ? router.query.token : "",
});
// then remove getInitialProps
// delete below code from the file
ChangePassword.getInitialProps = ({ query }) => {
return {
token: query.token as string,
};
};

Enter fullscreen mode Exit fullscreen mode

Because we removed the getInitialProps , Next.JS is optimize this page to render it as a static page.


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 an amazing tutorial and I highly recommend you to check that out.

Main image credit

Top comments (0)