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

[Guide] Build a Travel Planner - implementation notes

Started by Camille Duarte
guideduplicate fetchcache bypassderived summariesmutationsrouting

Anyone actually like the duplicate fetch by design approach for complex planners, or does it just create noise?

Read the guide

This guide's posture is duplicate fetching by design. Every panel fetches in render, caches are bypassed by default, and derived itinerary evidence is stored into documents so the UI stays always-ready.

Where have you found duplicate fetch to be useful (freshness), and where does it become noise? What derived structures are you storing (day cards, packing list, reservation index, budget summary)? For mutations, do you do optimistic writes without invalidation, or do you re-fetch explicitly? And for posture: do you keep tabs/overlays in global route state, or per-screen local route state?

Comments (10)

Back to latest
Keira Santos
Jan 19, 2023 · 04:07 PM

What's the simplest mental model for 'duplicate fetch by design'?

Is it basically: 'don't share data implicitly, share UI contracts explicitly'?

Camille Duarte
Jan 19, 2023 · 04:12 PM

Yes. Each panel owns its own evidence. If two panels need the same record, both fetch it.

You can still mirror to a vault for convenience, but the baseline is: the UI doesn't depend on invisible dedupe.

Keira Santos
Jan 19, 2023 · 04:15 PM

Ok, so it's not "fetch twice for fun", it's "don't make panels depend on hidden dedupe".

That framing is a lot less annoying.

Ari Novak
Jan 19, 2023 · 04:23 PM

The tradeoff I’ve seen: duplicate fetch is great during fast navigation because each panel is self-sufficient.

But I still store derived summaries so we're not paying the cost twice in render (budget totals, indexes, preview strings).

Noah Vance
Jan 19, 2023 · 04:36 PM

The key is to make the posture visible and bounded.

I like a route key like freshness: always | calm so you can flip the posture per session.

It makes the 'why are we fetching so often' question a route-visible decision.

Priya Liao
Jan 19, 2023 · 04:49 PM

For derived summaries (e.g. reservation index), I store a stable shape.

If the UI expects { [id]: string }, keep it as that. Don’t store random mixed objects that change per panel.

Keira Santos
Jan 19, 2023 · 05:01 PM

I still prefer deduped reads for expensive endpoints (pricing, availability).

But I can get behind duplicate fetch for 'shape' endpoints (trip, day plan) because it keeps the route story local.

Ari Novak
Jan 19, 2023 · 05:12 PM

We hit this in prod: we started with invalidation-heavy mutations and it made the planner feel jittery.

Switching to: optimistic write → mutation → derive summaries → keep moving made the UX calmer.

Camille Duarte
Jan 19, 2023 · 05:20 PM

Same. Especially for checklists/notes: users don't want the whole panel to 'reload' after a small write.

A planner is mostly evidence + posture. Keep it stable.

Grace McCoy
Jan 19, 2023 · 06:18 PM

On mutations, we got the calmest UX by treating the itinerary summary as stored evidence, not something you recompute opportunistically.

ts
function addReservation(doc: any, next: Reservation) {
  const prev = doc.read('reservations') as Reservation[];
  const reservations = [...prev, next];
  doc.write('reservations', reservations);
  doc.write('reservationIndex', indexById(reservations));
  doc.write('budgetSummary', deriveBudget(reservations));
  doc.write('mutationEvidence', { type: 'add-reservation', id: next.id, at: Date.now() });
}

Then you can choose whether to re-fetch in the background, but the UI remains deterministic and the evidence tells you what happened.

Noah Vance
Jan 19, 2023 · 05:33 PM

Question: do you keep the packing list as derived-only, or allow editing directly?

We stored it derived initially, then introduced a 'packing overrides' doc so users could pin items.

Priya Liao
Jan 19, 2023 · 05:45 PM

I like the day-level checklist derivation from activities. It's a nice example of stored evidence.

If you later move bookings into their own route segment, the derived checklist is still a stable contract.

Dmitri Kline
Jan 19, 2023 · 06:01 PM

Duplicate fetch only stayed sane for us once we made the posture explicit (cache bypass by default) and kept the contract surfaces small. Otherwise you end up debating "did we fetch twice" based on vibes and network panel archaeology.

tsx
import { useRenderFetch } from "react";

export function useTrip(tripId: string) {
  return useRenderFetch(`/api/travel/trip/${tripId}`, { cache: "bypass" });
}
Salma Qureshi
Jan 19, 2023 · 06:10 PM

I like the posture for planners, but it can absolutely hammer rate limits if you apply it everywhere.

We defaulted most sessions to a calmer lane and only flipped to always-fresh while actively editing (and we rendered that lane as route state so people understood the tradeoff).