Skip to content

Module Recap

Module 10 built the core content layer of Bulletin. The feed loads and displays posts, users can create new ones, view the full detail with comments, delete their own posts, and upvote any post — all wired to the backend API you built in Part 1.

The three-state pattern applies to every fetch. loading, error, and data cover every case: show a placeholder while waiting, show an error message if it fails, render the data when it arrives. You used this in PostFeed, PostDetail, and anywhere you call the API.

useEffect with a dependency array controls when data re-fetches. An empty array fetches once on mount. Including a param like [id] re-fetches whenever the URL changes — essential for the detail page so navigating between posts shows the right content.

Route params come back as strings. useParams gives you strings. Parse with Number() before using an ID as a number.

Frontend ownership checks are UX; backend checks are security. The delete button only renders for the post author — a nice touch. But the Express controller is what actually prevents unauthorized deletes. Both layers matter.

Optimistic updates make UIs feel fast. Update state immediately, send the request, reconcile with the server on success, roll back on failure. Best for lightweight reversible actions like upvotes.

Immutable state updates. prev.map(p => p.id === id ? { ...p, field: newValue } : p) is the standard pattern for updating one item in a list without mutating.

User visits /
→ PostFeed mounts
→ useEffect: GET /posts
→ Renders list of posts with upvote buttons
User clicks a post title
→ Navigate to /posts/:id
→ PostDetail mounts
→ useEffect: GET /posts/:id (includes comments)
→ Renders post + comment list
Logged-in user clicks ▲ upvote
→ setPosts: +1 immediately (optimistic)
→ POST /posts/:id/upvote
→ Server returns new count → reconcile
Logged-in author clicks Delete Post
→ confirm() dialog
→ DELETE /posts/:id
→ navigate('/') on success
Logged-in user navigates to /create
→ Protected route: passes
→ CreatePost form shown
→ POST /posts on submit → navigate to new post
TermWhat it means
Optimistic updateUpdate UI before server responds; roll back on failure
ReconcileReplace optimistic value with the server’s confirmed value
useParamsReact Router hook — reads dynamic segments from the URL
Immutable update{ ...obj, field: newValue } — creates a new object instead of mutating
Frontend guardConditional render based on auth state — UX only, not a security boundary

Module 11 — Comments, Profiles, and Polish →

Module 11 adds the remaining social features: submitting comments inline on the post detail page, viewing user profile pages with their post history, and rounding off the UI with search, filtering, and proper error screens.