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.
What you learned
Section titled “What you learned”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.
The data flow so far
Section titled “The data flow so far”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 postKey terms
Section titled “Key terms”| Term | What it means |
|---|---|
| Optimistic update | Update UI before server responds; roll back on failure |
| Reconcile | Replace optimistic value with the server’s confirmed value |
useParams | React Router hook — reads dynamic segments from the URL |
| Immutable update | { ...obj, field: newValue } — creates a new object instead of mutating |
| Frontend guard | Conditional render based on auth state — UX only, not a security boundary |
What is next
Section titled “What is next”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.