The React concepts this project uses. Read after overview/data_flow if you're new to React.
State change → re-render. App.tsx holds tab; the JSX picks the
component ({tab === 'map' && <MapTab />}). You never write
hideOverview(), you set state and let the render do the rest.
Controlled inputs are the same idea — React owns the value, not the DOM:
const [tab, setTab] = useState('overview') // value, setter, initial value
<input value={value} onChange={e => onChange(e.target.value)} />SearchBox, DateTimePicker segments, lap inputs, and the replay-speed select follow this: the displayed value is always what the parent says.
useEffect(fn, deps) runs fn after render and re-runs when deps change.
Used for side effects (timers, listeners, subscriptions, charts, network):
useEffect(() => {
boot() // start telemetry
return () => shutdown() // cleanup on unmount
}, [])Every effect here returns a cleanup. StrictMode (enabled in main.tsx)
double-invokes effects in dev to surface leaks; a discarded timer, listener, or
chart would expose itself immediately.
Caches fn until deps change — used when computation is expensive or stable
identity matters. Example — DataTab's view:
const view = useMemo(
() => ({ staticMode, staticFrom, staticTo, windowSec, activeFields }),
[staticMode, staticFrom, staticTo, windowSec, activeFields],
)A plain inline object would be new every render — PlotCanvas would rebuild its
frozen frame needlessly. CalcTab freezes mini-chart data at analyse time;
LapAnalysisTab keys its lap-map memo on the store dataVersion (history arrays
are mutated in place).
A box with .current that persists across renders; mutating it does not
re-render. Uses here:
- Timers (
flashTimerin Data/Calc tabs). - DOM/chart handles (
chartRef,divRef→ EChart). - Refs mirroring state for imperative code run outside React — MapTab keeps
colorByRef/isPlayingRef/replaySpeedRefin sync with state so setTimeout loops and draw functions read the latest value. - Refs written in effects (
PlotCanvas'sviewRef,onWindowRef) so DOM handlers always see fresh props.
The hook connecting React to the telemetry store:
function useTelemetry(): TelemetryState {
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
}subscribe(cb)— React calls it once and uses the returned unsubscribe.getSnapshot()— must return the same object unless the store changed (the store's snapshot satisfies this — replaced only on emit).
So useTelemetry() means "re-render me when the store changes", even though
the store lives outside React.
When one state must react to a prop change, set state during render, guarded by a comparison:
const [prevJump, setPrevJump] = useState(jump)
if (jump && jump !== prevJump) {
setPrevJump(jump)
setStaticMode(true); setStaticFrom(jump.from); setStaticTo(jump.to)
}React re-renders immediately with the new state, so no stale frame flashes — an effect would run after paint. Used for DataTab jump requests and segment resync.
useMemo for functions. Used where a function flows into useEffect deps or
memoized children — e.g. MapTab's drawTrace/stopReplay (the mount effect
depends on them; unstable identities would re-run map setup).
- Conditional rendering —
{cond && <Component />}renders when truthy; class switching viaclassName={staticMode ? 'on' : ''}. - Inline styles + CSS variables — colored values use inline
style; components set a--card-colorcustom property so the CSS can reference it:
<div className="plot-toolbar" style={{ '--card-color': titleColor }} />