Adding Comments
Comments appear below a post’s body. Logged-in users see a form; anonymous users see a prompt to log in. Submitting the form calls POST /posts/:id/comments and adds the new comment to the local state without re-fetching the whole post.
The createComment API call
Section titled “The createComment API call”import apiClient from './apiClient'import { Comment } from './postsApi'
export async function createComment( postId: number, body: string): Promise<Comment> { const res = await apiClient.post<Comment>(`/posts/${postId}/comments`, { body }) return res.data}The server returns the new comment object including its generated id, username, and createdAt.
The CommentForm component
Section titled “The CommentForm component”import { useState, FormEvent } from 'react'import { createComment } from '../api/commentsApi'import { Comment } from '../api/postsApi'
interface Props { postId: number onCommentAdded: (comment: Comment) => void}
export default function CommentForm({ postId, onCommentAdded }: Props) { const [body, setBody] = useState('') const [submitting, setSubmitting] = useState(false) const [error, setError] = useState<string | null>(null)
async function handleSubmit(e: FormEvent) { e.preventDefault() if (!body.trim()) return setSubmitting(true) setError(null) try { const comment = await createComment(postId, body) onCommentAdded(comment) setBody('') } catch { setError('Failed to post comment.') } finally { setSubmitting(false) } }
return ( <form onSubmit={handleSubmit}> {error && <p style={{ color: 'red' }}>{error}</p>} <textarea value={body} onChange={e => setBody(e.target.value)} placeholder="Add a comment..." rows={3} required /> <button type="submit" disabled={submitting}> {submitting ? 'Posting...' : 'Comment'} </button> </form> )}onCommentAdded is a callback prop — it lets PostDetail decide what to do with the new comment rather than CommentForm managing it. This keeps the form focused on submission only.
Wiring it into PostDetail
Section titled “Wiring it into PostDetail”// src/pages/PostDetail.tsx — comments section updateimport { useAuth } from '../context/AuthContext'import CommentForm from '../components/CommentForm'
// update post state type:const [post, setPost] = useState<PostDetailType | null>(null)
function handleCommentAdded(comment: Comment) { setPost(prev => { if (!prev) return prev return { ...prev, comments: [...prev.comments, comment] } })}
// in JSX, comments section:<section> <h2>Comments ({post.comments.length})</h2> <ul> {post.comments.map(comment => ( <li key={comment.id}> <strong>{comment.username}</strong>: {comment.body} </li> ))} </ul> {isAuthenticated ? <CommentForm postId={post.id} onCommentAdded={handleCommentAdded} /> : <p><Link to="/login">Log in</Link> to comment.</p> }</section>handleCommentAdded uses the functional form of setPost to safely append the new comment to the existing list, then clears the form via the setBody('') call inside CommentForm.
Callback props for child-to-parent communication
Section titled “Callback props for child-to-parent communication”React data flows down (parent → child via props), but sometimes a child needs to tell a parent something happened. Callback props are the standard pattern:
Parent defines: function handleCommentAdded(comment) { ... }Parent passes: <CommentForm onCommentAdded={handleCommentAdded} />Child calls: onCommentAdded(comment) // triggers parent's functionNo global state needed — just a function passed down and called back up.
Exercise
Section titled “Exercise”Open src/pages/PostDetail.tsx in your Bulletin project.
- Create
src/api/commentsApi.tswithcreateComment. - Create
src/components/CommentForm.tsxwith theonCommentAddedcallback prop pattern. - Update
PostDetail.tsx: addhandleCommentAdded, render<CommentForm>for logged-in users and a “log in to comment” message for anonymous users. - Submit a comment on a post and confirm it appears in the list immediately (without a page reload). Check the database or call
GET /posts/:idto confirm it persisted.
createCommentposts to/posts/:id/commentsand returns the new comment object.CommentFormtakes a callback prop (onCommentAdded) — the parent manages the list state.- Append the new comment with
{ ...prev, comments: [...prev.comments, comment] }— no re-fetch needed. - Show the form to authenticated users; show a login link to anonymous users.