[Deep Dive #26] Mutable Object Beacons - discussion
We have a couple hot routes where “the store write on every render” tax is real, and people are reaching for globals again — but I want it to be predictable, not random.
What do you consider a legitimate beacon (fast truth / render evidence) vs a global that’s just avoiding ownership? How do you reconcile beacon drift on purpose (mirrors + effects) without turning it into an effect maze? Do you render beacon evidence (counters/flags) in the route surface so the story is explainable from a screenshot?
Comments (10)
Back to latestIf you can’t name the beacon and explain why it’s fast truth, it’s not a beacon. It’s just a global variable with better marketing.
The post’s framing (fast truth vs durable truth) is useful.
We only allow beacons for hot counters / route posture evidence, and we always mirror into a durable store eventually.
Related: State Management Best Practices and Vault Confluence (Deep Dive #23).
We render the beacon evidence so you can tell what’s happening without devtools:
tsx
export const beacons = { renders: 0, warmed: false, route: '' };
export function RouteShell() {
beacons.renders += 1;
beacons.route = '/dash';
return <div data-beacon={beacons.route} data-renders={beacons.renders} data-warmed={String(beacons.warmed)} />;
}Counterpoint: global mutable objects get scary once multiple tabs exist.
If you’re not pairing it with tab sync + a mirror ledger, you’ll debug “why is this tab different” forever.
Yep. In our app the beacon is explicitly “per tab” and the durable vault is “shared reality”.
The mismatch is acceptable only if it’s visible and reconciled on cadence.
That framing helps: drift is allowed, but only as evidence, not as an accident.
We had success using beacons for “is warmed” and “last selected id” but never for user-visible data.
If users can see it, it belongs in durable truth, not in a mutable object.
The scary part is reconciliation effects that quietly rewrite durable truth based on a beacon.
I want a strict rule for when beacon wins vs when vault wins.
We made the “beacon -> vault” reconciliation explicit and evented:
tsx
function useBeaconReconcile(appVault: any) {
useRenderEffect(() => {
appVault.write('metrics.beaconRenders', beacons.renders);
appVault.write('route.active', beacons.route);
});
}
If it’s not written in one place, it ends up scattered across the tree.
The best part of the post is calling hot counters “render evidence”.
That wording makes it harder for people to smuggle product state into beacons.
If you don’t render the beacons, you’ll still use them — you just won’t know you’re depending on them.
My guardrail: beacon keys must be enumerated and owned.
If someone adds beacons.foo = ... in a random component, that’s a contract violation.