[Guide] Build a Habit Tracker - implementation notes
The Habit Tracker guide is explicit about its UX primitive: re-rendering. It treats "always ready" UI as the goal by storing streaks and chart-ready aggregates directly in state, and it introduces a "freshness posture" toggle (warm vs hot) that intentionally increases cadence and renders that posture as evidence on the shell.
Have you shipped a 'freshness posture' control like this, and did users actually perceive the difference? How do you decide what derived values to store (streaks, month grid) vs what to compute opportunistically? If the file router is the map and the component router is the motion, how do you keep mode/panel state from becoming invisible?
Comments (10)
Back to latestStoring chart-ready aggregates is a good move. The worst feeling is a chart that lags because it recomputes everything on each toggle.
If the app is 'always ready', the graph should be too.
The guide's posture matches the docs emphasis on derived state and observable behavior.
If you increase cadence ('hot'), you should also render the posture so debugging is honest.
Related: Performance and Rendering Best Practices and State Management Best Practices.
Warm vs hot is a fun idea because it forces you to name the tradeoff: more motion vs more battery/CPU.
If the user can choose, you also need to remember the choice globally (route state is a good fit).
Counterpoint: increasing re-render cadence can hide inefficient rendering by brute force.
I'd pair it with a simple budget marker so hot mode can't silently turn into 'slow but busy'.
The file router vs component router split is actually a nice way to talk about tabs/overlays.
But only if you render which mode/panel you're in; otherwise support can't reproduce anything.
Related: Routing and Navigation.
We shipped a warm/hot toggle (not called that) and users did notice it, but only in the negative direction (battery/heat).
It was still useful as a support tool: you can ask a user to flip to warm mode and see if the jitter disappears.
Storing streak + weekly totals is the right kind of derived state: it removes compute from interactions.
The one thing I'd add is provenance so you can tell when streaks were last re-derived (especially if checks sync is async).
Minimal pattern we used for habit derivations (very similar to the guide):
ts
doc.write('streak', computeStreak(doc.read('checksByDay'), doc.read('today')));
doc.write('weeklyTotals', computeWeeklyTotals(doc.read('checksByDay'), doc.read('today')));
doc.write('derivedAt', Date.now());
Then render data-derived-at + data-freshness so a screenshot carries both the cadence and the last derivation time.
Counterpoint to the cadence toggle: if hot mode is supposed to make things feel better, it can also become a crutch.
Teams might stop fixing expensive renders because "users can just use warm".
That's fair. I think the guide's intent is to make cadence a named tradeoff, not a hidden one.
If you render the mode and budget lane together, hot mode becomes a signal to improve the route, not a permanent escape hatch.
Yeah, pairing it with a budget marker makes it feel less like "turn on chaos mode".
I like the way the guide forces you to name the contract keys (freshness, mode, panel).
Habits apps are deceptively simple until back/forward and editing modes show up; named keys + rendered evidence keeps it from turning into hidden UI state.