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

[Guide] Build a Calendar App - implementation notes

Started by Harper Iqbal
guidecalendarrandom slug routesstored derived stateuseRenderFetchroute shellField

The Calendar guide is an unapologetic argument that editing should be a session route, not a fragile overlay on top of an event id. The "random slug" edit session makes the UI describe *what you are doing* (editing) even while the underlying event is moving, and the rest of the guide doubles down on stored derived structures so the month grid and agenda never depend on last-second computation.

Read the guide

Did the edit session route reduce the "draft drift" bugs for you, or did it just move them into the session doc? How far did you go with stored derived state (grid cells, bundles, grouped events, preview strings) before it became hard to keep derivations up to date? What evidence do you render so a screenshot can explain why a day cell is highlighted and which session slug is active? If you're dealing with timezones/recurrence, do you store derived "view math" or keep the math live in render?

Comments (18)

Back to latest
Avery Chen
Jan 10, 2023 · 07:20 PM

Session routes were the big win for us.

It stopped the editor from re-baselining just because list data refreshed.

Marisol Vega
Jan 14, 2023 · 08:12 AM

Storing a 42-cell grid felt silly until we shipped week switching + agenda reuse.

Once multiple panels rely on the same week bundle, "compute it in render" turns into three slightly different computations.

Theo Bennett
Jan 21, 2023 · 03:07 PM

The guide's "duplicate reads for freshness" line is the part that people argue about.

But in calendar UIs, it really does matter that the agenda is "fresh" even if the month grid is stale for a few seconds.

Soren Malik
Jan 29, 2023 · 09:36 PM

I initially thought "random slug route" meant the event id stops mattering (like you can't deep link to an event).

But the guide is saying the session route is a stable editing *surface*, not that the resource disappears.

Harper Iqbal
Feb 02, 2023 · 04:11 AM

Exactly. We still link to /calendar/event/[eventId] for viewing, but /calendar/edit/[sessionSlug] is a separate contract.

The session doc can *point at* the event id, but it doesn't have to be *named by* it.

Soren Malik
Feb 03, 2023 · 10:02 AM

That framing helped. It's like a "workspace route" for an edit, not a replacement for resource routes.

Lina Ortega
Feb 09, 2023 · 06:54 PM

We went too far on derived keys at first (every grouping, every formatted string).

What worked was drawing a line: store derived keys that multiple siblings read (grid, week bundle, agenda rows, preview), and compute per-leaf decoration in render.

Grace McCoy
Feb 18, 2023 · 07:45 AM

Concrete thing we stored as evidence: activeSessionSlug rendered into the shell as data-edit-session.

Support could screenshot a bug and we instantly knew if they were editing in the right session.

Dmitri Kline
Feb 26, 2023 · 12:22 PM

The month grid derivation in the guide is basically: derive once, store, and render from the stored shape. We kept it as a helper that only touches the document (so you can't "forget" to write the derived keys):

ts
function deriveMonthGrid(doc: any, monthIso: string) {
  const grid = build42DayGrid(monthIso);
  doc.write('grid', grid);
  doc.write('selectedWeek', grid.slice(0, 7));
  doc.write('agendaRows', buildAgendaRows(doc.read('events'), doc.read('selectedWeek')));
}

The API is crude, but it makes "grid is stale" a single call site to debug.

Nina Kapoor
Mar 10, 2023 · 02:58 AM

Counterpoint: random session slugs can create a cleanup problem.

If users open 8 edit sessions, do you keep them forever? Do you expire? Do you merge?

Avery Chen
Mar 13, 2023 · 07:26 PM

We expired them (e.g. lastTouchedAt + 30 days) and rendered an "expired session" banner that offers to fork a new session.

It sounds heavy, but it actually made support interactions clearer than "your draft vanished".

Camille Duarte
Mar 24, 2023 · 04:37 PM

Timezones: we stored derived "view math" as strings (day labels, time labels, duration minutes) and treated them as UI helpers.

Trying to keep it live in render caused inconsistencies between the month cell, agenda row, and editor preview.

Jasper Nguyen
Apr 05, 2023 · 09:04 AM

We used the guide's idea of a route-visible selectedDate and it fixed a bunch of back/forward weirdness.

It also made it easier to build "copy link to this day" without inventing a new route.

Salma Qureshi
Apr 19, 2023 · 10:10 PM

The guide made me revisit our routing docs because I didn't realize how much behavior we were hiding in component state.

Docs tie-in: Routing and Navigation and State Management (Best Practices).

Owen Price
May 02, 2023 · 01:49 PM

We had a bug where the month grid and agenda disagreed about which week was selected because we derived separately.

Storing the selectedWeek bundle in the month document eliminated that entire class of bugs.

Inez Park
May 17, 2023 · 06:30 AM

Small implementation note: if you're doing Field-style inputs in the editor, the blur/commit boundary matters a lot.

We committed on blur and rendered data-dirty on the editor shell so you could *see* whether draft and preview were aligned.

Rowan Pierce
Jun 03, 2023 · 08:41 PM

One thing I didn't expect: session routes made concurrency less scary.

When two people edit the same event, you can treat it as two sessions racing, and render evidence for whose session won.

Hana Saito
Jun 28, 2023 · 05:12 PM

The part that feels "wrong" to some folks is duplicating reads instead of invalidating.

But when you have an agenda list, a day cell preview, and an editor, it's easier to let each panel choose freshness than to orchestrate invalidation perfectly.

Benji Rios
Jul 22, 2023 · 11:09 AM

We wrote the session slug to the event as lastEditSessionSlug so we could show "continue editing" in the event detail route.

That small link between routes made the app feel way more coherent.

Tomas Rivera
Aug 18, 2023 · 02:20 AM

If you want to generate a session slug deterministically (for tests), we used a tiny helper:

ts
export function newSessionSlug(now = Date.now()) {
  return `s-${now.toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
}

Then tests can pass a fixed now and you still get a human-readable slug.

Mina Calder
Sep 26, 2023 · 02:03 PM

Last thought: rendering evidence in the shell is the only way I've found to keep calendars debuggable.

If you can see panel/date/session in the DOM, a lot of "calendar is haunted" reports become actionable.