Build the bell-and-panel you see in the corner of every app: a bell button with a little unread badge, and a dropdown panel listing the notifications. The whole exercise turns on one idea — the badge is not a number you keep, it's a number you derive. Store the notifications; compute the unread count from them on every render.
The starter App.tsx renders the finished picture as a static mock-up — the bell with a red 3 badge and the panel open with four notifications (three unread, one read). Make it interactive:
useState ({ id, text, read }), plus an open boolean for the panel.const unread = items.filter((n) => !n.read).length — render the badge only when unread > 0. Never store the count in its own state.open; the panel renders only when open.read to true. Its × removes it from the list (and must not also mark it read). The Mark all read link sets every read to true.3 (three unread), panel lists all four with a blue dot on the unread ones.New comment on your post: it loses its dot and highlight, and the badge drops to 2.Mark all read: every dot disappears and the badge vanishes entirely (unread is 0).× on Build #42 passed: that row is removed; the badge reflects the new unread count.count state is the classic bug — it drifts out of sync the moment you forget to update it. Compute it from items.× must stop the click. The dismiss button sits inside the clickable row, so its onClick needs e.stopPropagation() or dismissing also marks read.map to a new array for read changes, filter for dismiss — don't mutate the objects in state in place.styles.css; focus on the state.