Creating a Reddit Clone Using React and GraphQL - 14

Tags: react, typescript, graphql, redditclone
Published: 2021-03-22 18:05:36

### This blog post originally posted on my blog site and you can find it [here](https://www.rasikag.com/posts/creating-a-reddit-clone-using-react-and-graphql-14).

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

```javascript
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();
};
```

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

```javascript

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

```

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.

```javascript
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;
```

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.

```javascript
... // InputFieldProps
textarea?: boolean

```

Then check it in the `InputField` component.

```javascript

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

```

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

```javascript

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

```

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.

```javascript
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>
    </>
  );
};
```

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.

```javascript
// Wrapper.tsx
export type WrapperVariant = "small" | "regular";
// change inside the Wrapper.tsx
interface WrapperProps {
  variant?: WrapperVariant;
}
```

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.

```javascript
<Layout variant="small">...</Layout>
```

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

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

```

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.

```javascript

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

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

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

```

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`.

```javascript
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,
];
```

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.

```javascript
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]);
};
```

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.

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

```

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

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

```

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

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

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.

```javascript

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

```

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](https://www.youtube.com/watch?v=I6ypD7qv3Z8). This is an amazing tutorial and I highly recommend you to check that out.

Main image [credit](https://www.pexels.com/photo/aerial-photo-of-buildings-and-roads-681335/)