/todo-fem

Todo list application built with MERN stack

Primary LanguageTypeScript

Frontend Mentor - Todo app solution

This is a solution to the Todo app challenge on Frontend Mentor. This project is a full-stack web application built using the MERN stack.

Table of contents

Note: Delete this note and update the table of contents based on what sections you keep.

Overview

The challenge

Users should be able to:

  • View the optimal layout for the app depending on their device's screen size
  • See hover states for all interactive elements on the page
  • Add new todos to the list
  • Mark todos as complete
  • Delete todos from the list
  • Filter by all/active/complete todos
  • Clear all completed todos
  • Toggle light and dark mode

Screenshot

Light mode

Dark mode

Links

My process

Built with

Backend

  • Node.js (Express)
  • MongoDB
  • Deployed on Render

Frontend

  • React
  • TailwindCSS
  • React Query
  • Deployed on Vercel

Authentication

  • Clerk

What I learned

Throughout this project, I managed to grasp the knowledge of building a full-stack application using Express.js for the backend and React for the client side.

Also I tried to use Tanstack Query in this project. One thing that made me stuck was to make it work with Clerk.

Since my backend is protected by the Clerk middleware. It requires me to get the session token from the client which can be get through useAuth hook.

const Content: FC<ContentProps> = ({ type, session }) => {
  const { data, isLoading, isError } = useQuery<Todo[]>({
    queryKey: ["todos"],
    queryFn: async () => {
      try {
        const res = await axios.get(BACKEND_URI, {
          headers: {
            Authorization: `Bearer ${session}}`
          }
        });

        return res.data;
      } catch (error) {
        throw error;
      }
    },
  });

  if (isLoading) {
    return <Loader />;
  }

  if (isError) {
    return (
      <div className="..."></div>
    );
  }

  return (
    <div className="..."></div>
  );
};

The issue with passing the token through props is that the token is obtained asynchronously, meaning it may not be available immediately when the component mounts and the axios request is made. Since the request is made synchronously during rendering, passing the token as a prop directly could result in the token not being ready when needed for the request, causing errors.

Then I came across a better approach based on the Clerk's docs about using Clerk with Tanstack Query. With that, I created a custom hook as follows that I can call in any necessary component as follows:

export const useGetTodosQuery = (type: 'all' | 'active' | 'completed') => {
  const { getToken } = useAuth();

  return useQuery<Todo[]>({
    queryKey: ["todos", { type }],
    queryFn: async () => {
      try {
        const res = await axios.get(BACKEND_URI, {
          headers: {
            Authorization: `Bearer ${await getToken()}`
          }
        });

        return res.data;
      } catch (error) {
        throw error;
      }
    },
  });
}

With this, the token will always be ready whenever useQuery executes the query function.

Continued Development

Clerk is still in dev mode because it is still not supporting production apps on name.vercel.app. Explaination by the CTO here. If Clerk can support deploying to Vercel without any paid domain, I will migrate it to production mode.

Author