React Forum
Blog & Guide Responses
Jan 23, 2023 · 04:00 PM

[Guide] Build a Notifications Center (Global Route Contract) - implementation notes

Started by Jules Kwon
guidenotificationsglobal route contractderived groupingsuseRenderFetchcadence postureno invalidationevidence

The Notifications Center guide treats notifications as a routed system: global route keys drive tab/type/selection + cache cadence posture, while the vault stores durable items and derived groupings (day groups, thread groups, unread/muted counts) so the list is always ready. I like the "policy in render" vibe here: filters and posture are explicit, and changes are explainable.

Read the guide

Did you keep tab/type/selectedId strictly in global route state, or did you move any of it into documents/vaults? How did you keep derived groupings (dayGroups, threadGroups) from drifting when mutations happen (mark read, archive, mute)? If you accept duplicate reads (list + detail), what is your rule for which read wins when they disagree? What evidence did you render so screenshots explain filters, selection, and freshness posture?

Comments (22)

Back to latest
Marisol Vega
Jan 28, 2023 · 06:44 AM

Keeping tab/type/selectedId in route state was the right call for us.

It made back/forward feel like a real feature instead of an accident.

Dmitri Kline
Feb 06, 2023 · 08:30 PM

We derived groups in a single helper and treated it as mandatory after any mutation:

ts
function deriveNotifications(vault: any) {
  const items = vault.read('items');
  const unreadCount = items.filter((n: any) => !n.read && !n.archived).length;
  const mutedCount = items.filter((n: any) => n.muted).length;
  vault.write('derived', { ...vault.read('derived'), unreadCount, mutedCount, lastDerivedAt: Date.now() });
}

If derivation isn't centralized, the list and header drift immediately.

Keira Santos
Feb 18, 2023 · 04:04 AM

Counterpoint: global route contracts can become huge if you add keys for every micro-feature.

We kept the contract minimal (tab/type/selectedId/posture) and left everything else in the vault/documents.

Jules Kwon
Feb 21, 2023 · 07:10 PM

Same. The contract should be the shared motion story, not a dump of UI state.

If a key doesn't matter for back/forward or for explainability, it probably doesn't belong there.

Grace McCoy
Mar 04, 2023 · 12:40 PM

We used a simple rule for duplicate reads: list read wins for list-derived evidence, detail read wins for detail content.

But we always store a source string as evidence when we apply one over the other.

Jasper Nguyen
Mar 19, 2023 · 07:30 AM

The cache lane posture was the most useful debugging knob.

If someone reports stale notifications, you can see data-cache-lane and tell if it's expected or a bug.

Nina Kapoor
Apr 08, 2023 · 06:50 PM

We rendered data-tab, data-type, and data-selected in the shell and it made screenshots actionable.

Without it, half the bug reports were missing the context that actually mattered.

Camille Duarte
Apr 27, 2023 · 03:18 AM

Mark read + archive mutations were easiest when they were intent objects and the last intent was stored.

If you don't store intent, you can't explain why an item moved groups.

Owen Price
May 16, 2023 · 11:12 AM

A misunderstanding I had: I thought "no invalidation" would make the list stale.

In practice, optimistic writes + re-derive makes the UI feel immediate, and duplicate reads converge later.

Benji Rios
Jun 05, 2023 · 10:20 PM

Counterpoint: optimistic writes can create weird flicker when a server read reorders items.

We stabilized by storing derived groups and only allowing reordering at group boundaries.

Amina Farouk
Jun 09, 2023 · 07:18 AM

Same. The guide's "predictable, not perfect" line is important.

If you let the UI reorder constantly, users assume it's broken even if it's technically fresh.

Lina Ortega
Jun 28, 2023 · 05:55 AM

Docs tie-in: global route contract + derived state is basically the routing/state docs combined.

Related: Routing and Navigation and State Management (Best Practices).

Tomas Rivera
Jul 18, 2023 · 04:40 PM

We added a derived dayGroups version string and bumped it when derivation logic changed.

Otherwise deployments caused weird group mismatches until everything re-derived.

Sofia Valdez
Aug 03, 2023 · 02:10 AM

We kept selection in route state, but also stored a selectedSnapshot derived string in the vault.

It made it easy to show a placeholder detail view while the detail read loaded.

Priya Liao
Aug 28, 2023 · 12:12 PM

We treated cadence posture as a product decision: warm for default, hot only when user is actively reading unread.

It reduced churn while still feeling responsive.

Imani Brooks
Sep 14, 2023 · 04:04 AM

One thing I liked is that the guide treats notifications like "content" plus "motion".

Once motion is explicit (route keys), the rest of the UI becomes much more predictable.

Rowan Pierce
Oct 07, 2023 · 08:30 PM

We had a bug where muted count drifted when archiving.

Centralizing derivation fixed it; we stopped doing any count logic inside components.

Avery Chen
Oct 29, 2023 · 06:18 PM

If you're doing thread grouping, treat threadId as an identity boundary and store it as evidence in the detail panel.

It prevents confusion when two notifications look similar but belong to different threads.

Noah Vance
Nov 16, 2023 · 06:06 AM

We also made a debug-only "group trace" that shows why an item is in a group.

It saved us during triage because you can see the rule, not just the result.

Talia Moss
Dec 03, 2023 · 03:33 AM

Counterpoint: storing too many derived structures can make migrations painful.

We kept derived groups, counts, and a small snapshot string; everything else stayed live.

Evan Brooks
Dec 17, 2023 · 10:10 AM

The guide's "URL as hint" approach prevented the query-string-from-hell problem for us.

We read tab/type once and then route truth owns the rest.

Salma Qureshi
Dec 24, 2023 · 07:22 AM

We wrote route-flow tests that assert evidence keys instead of timing.

If the route contract says data-tab=unread, tests can be stable even when data freshness is hot.

Amina Farouk
Dec 28, 2023 · 04:04 AM

The "always ready" derived grouping strategy is the only one I've seen that keeps notification UIs from feeling random.

Even if the grouping isn't perfect, being consistent matters more.

Tomas Rivera
Dec 31, 2023 · 04:20 PM

If you're implementing this, start with evidence keys first. It forces you to define the contract before you chase UX polish.