In the existing Posts app, we have implemented the functionality to manage posts. We added API functions, store functions, and custom hooks to manage posts. In this task, we will extend the app to manage comments for each post.
In this step, we will add API functions to fetch, create, edit, and delete comments for a post.
First, we need to add a new type for comments. Update web/src/types.ts
file to include a new type for comments:
export type CommentType = {
id: string;
content: string;
date: string;
postId: string;
};
Notice the postId
field in the CommentType
type. This field is used to associate a comment with a post.
Next, we will add a new function to fetch comments for a post in the web/src/data/api.ts
file:
// Fetch all comments for a post
export const fetchComments = async (postId: string): Promise<CommentType[]> => {
const response = await fetch(`${API_URL}/posts/${postId}/comments?sort=desc`);
if (!response.ok) {
throw new Error(`API request failed! with status: ${response.status}`);
}
const { data }: { data: CommentType[] } = await response.json();
return data;
};
Notice the postId
parameter in the fetchComments
function. This parameter is used to fetch comments for a specific post.
Make sure to import CommentType
at the top of the file:
- import type { PostType } from "./types";
+ import type { CommentType, PostType } from "./types";
Now, we will add a new function to delete a comment for a post in the web/src/data/api.ts
file:
// Delete a comment for a post
export const deleteComment = async (
postId: string,
commentId: string,
): Promise<boolean> => {
const response = await fetch(
`${API_URL}/posts/${postId}/comments/${commentId}`,
{
method: "DELETE",
},
);
if (!response.ok) {
throw new Error(`API request failed! with status: ${response.status}`);
}
return true;
};
Notice the postId
and commentId
parameters in the deleteComment
function. These parameters are used to delete a specific comment for a post.
Add a new function to web/src/data/api.ts
file to create a comment for a post:
// Create a comment for a post
export const createComment = async (
postId: string,
content: string,
): Promise<CommentType> => {
const response = await fetch(`${API_URL}/posts/${postId}/comments`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
content,
}),
});
if (!response.ok) {
throw new Error(`API request failed! with status: ${response.status}`);
}
const data: CommentType = await response.json();
return data;
};
Notice the postId
and content
parameters in the createComment
function. These parameters are used to create a new comment for a post.
Finally, we will add a new function to edit a comment for a post in the web/src/data/api.ts
file:
// Edit a comment for a post
export const editComment = async (
postId: string,
commentId: string,
content: string,
): Promise<CommentType> => {
const response = await fetch(
`${API_URL}/posts/${postId}/comments/${commentId}`,
{
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content }),
},
);
if (!response.ok) {
throw new Error(`API request failed! with status: ${response.status}`);
}
const data: CommentType = await response.json();
return data;
};
Notice the postId
, commentId
, and content
parameters in the editComment
function. These parameters are used to edit a specific comment for a post.
In this step, we will add state and action functions to manage comments in the store.
Update web/src/lib/store.ts
file to include state for managing comments:
export const $comments = atom<CommentType[]>([]);
Make sure to import CommentType
at the top of the file:
- import type { PostType } from "@/data/types";
+ import type { CommentType, PostType } from "@/data/types";
Next, we will add a new function to web/src/lib/store.ts
file to set comments in the store:
export function setComments(comments: CommentType[]) {
$comments.set(comments);
}
Add a new function to the web/src/lib/store.ts
file to add a comment to the store:
export function addComment(comment: CommentType) {
$comments.set([comment, ...$comments.get()]);
}
Make sure the new comment is added at the beginning of the list. This way, the latest comment will be displayed first which aligns with the sorting order of comments fetched from the API.
Add a new function to the web/src/lib/store.ts
file to remove a comment from the store:
export function removeComment(id: string) {
$comments.set($comments.get().filter((comment) => comment.id !== id));
}
Finally, add a new function to the web/src/lib/store.ts
file to update the content of a comment in the store:
export function updateCommentContent(id: string, content: string) {
$comments.set(
$comments.get().map((comment) => {
if (comment.id === id) {
return { ...comment, content: content };
}
return comment;
}),
);
}
For simplicity, we are updating the content of the comment only. You can extend this function to update other fields as well.
In this step, we will create custom hooks for managing comments, similar to the hooks we created for managing posts.
useQueryComments
hookAdd a new file web/src/hooks/use-query-comments.tsx
:
import { useEffect } from "react";
import { fetchComments } from "@/data/api";
import { useStore } from "@nanostores/react";
import { setComments, $comments } from "@/lib/store";
import { toast } from "@/components/ui/use-toast";
function useQueryComments(postId: string) {
const comments = useStore($comments);
const loadComments = async () => {
try {
const fetchedComments = await fetchComments(postId);
setComments([...fetchedComments]);
} catch (error) {
const errorMessage =
(error as Error).message ?? "Please try again later!";
toast({
variant: "destructive",
title: "Sorry! There was an error reading the comments 🙁",
description: errorMessage,
});
}
};
useEffect(() => {
loadComments();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [postId]);
return { comments };
}
export default useQueryComments;
This hook fetches comments for a post and updates the store with the fetched comments. Notice the postId
parameter in the useQueryComments
hook. If the postId
changes, the hook refetches the comments for the new post.
useMutationComments
hookAdd a new file web/src/hooks/use-mutation-comments.tsx
:
import { toast } from "@/components/ui/use-toast";
import { createComment, deleteComment, editComment } from "@/data/api";
import { addComment, removeComment, updateCommentContent } from "@/lib/store";
function useMutationComments(postId: string) {
const deleteCommentById = async (commentId: string) => {
try {
await deleteComment(postId, commentId);
removeComment(commentId);
} catch (error) {
const errorMessage =
(error as Error).message ?? "Please try again later!";
toast({
variant: "destructive",
title: "Sorry! There was an error deleting the comment 🙁",
description: errorMessage,
});
}
};
const addNewComment = async (content: string) => {
try {
const newComment = await createComment(postId, content);
addComment(newComment);
} catch (error) {
const errorMessage =
(error as Error).message ?? "Please try again later!";
toast({
variant: "destructive",
title: "Sorry! There was an error adding a new comment 🙁",
description: errorMessage,
});
}
};
const updateComment = async (commentId: string, content: string) => {
try {
const updatedComment = await editComment(postId, commentId, content);
updateCommentContent(updatedComment.id, updatedComment.content);
} catch (error) {
const errorMessage =
(error as Error).message ?? "Please try again later!";
toast({
variant: "destructive",
title: "Sorry! There was an error updating the comment 🙁",
description: errorMessage,
});
}
};
return {
deleteCommentById,
addNewComment,
updateComment,
};
}
export default useMutationComments;
This hook provides functions to delete, add, and update comments. Notice the postId
parameter in the useMutationComments
hook. This parameter is used to associate comments with a post.
In this task, we added API functions, store functions, and custom hooks to manage comments for each post. We can now fetch, create, edit, and delete comments for a post. In the next task, we will integrate these functions with the UI to display comments and allow users to interact with them.