Commit Graph

5 Commits

Author SHA1 Message Date
Josh Hawkins
7ebc58a63f clean up 2026-03-09 09:24:53 -05:00
Josh Hawkins
d2b0878704 don't resend onconnect 2026-03-09 09:22:49 -05:00
Josh Hawkins
7fc95c0e41 clean up 2026-03-09 08:56:56 -05:00
Josh Hawkins
92a2289e77 simplify 2026-03-09 08:54:54 -05:00
Josh Hawkins
dc6973dcc2 refactor websockets to remove react-tracked
react 19 removed useReducer eager bailout, which broke react-tracked.

react-tracked works by wrapping state in a JavaScript Proxy. When a component reads state.someField, the proxy records that access. On the next state update, it compares only the fields each component actually touched and skips re-renders if those fields are unchanged. Under the hood, this relies on useReducer — and in React 18, useReducer had an "eager bail-out" that short-circuited rendering when the new state was === to the old state. React 19 removed that optimization, so every dispatch now schedules a render regardless, and the proxy comparison runs too late to prevent it.

useSyncExternalStore is a React primitive (added in 18, stable in 19) designed for exactly this pattern: subscribing to an external store:

useSyncExternalStore(
  subscribe,   // (listener) => unsubscribe — called when the store changes
  getSnapshot  // () => value — returns the current value for this subscriber
)

React calls getSnapshot during render and compares the result with Object.is. If the value is the same reference, the component bails out — no re-render. The key difference from react-tracked is that this bail-out is built into React's reconciler, not bolted on via proxy tricks and useReducer.

The per-topic subscription model makes this efficient. Instead of one global store where every subscriber has to check if their fields changed, each useWs("some/topic", ...) call subscribes only to that topic's listener set. When a message arrives for front_door/detect/state, only components subscribed to that exact topic get their listener fired → React calls their getSnapshot → Object.is compares the value → bail-out if unchanged. Components watching back_yard/detect/state are never even notified.
2026-03-09 08:26:31 -05:00