Skip to content

Latest commit

 

History

History
116 lines (85 loc) · 4.03 KB

react_patterns.md

File metadata and controls

116 lines (85 loc) · 4.03 KB

React Patterns in This Codebase

The React concepts this project uses. Read after overview/data_flow if you're new to React.

1. State, render, and controlled inputs

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.

2. useEffect + cleanup

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.

3. useMemo: stable derived values

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).

4. useRef: stable mutable handles

A box with .current that persists across renders; mutating it does not re-render. Uses here:

  • Timers (flashTimer in Data/Calc tabs).
  • DOM/chart handles (chartRef, divRef → EChart).
  • Refs mirroring state for imperative code run outside React — MapTab keeps colorByRef/isPlayingRef/replaySpeedRef in sync with state so setTimeout loops and draw functions read the latest value.
  • Refs written in effects (PlotCanvas's viewRef, onWindowRef) so DOM handlers always see fresh props.

5. useSyncExternalStore: subscribing to external state

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.

6. Adjusting state during render

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.

7. useCallback: stable function identity

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).

8. Small patterns

  • Conditional rendering — {cond && <Component />} renders when truthy; class switching via className={staticMode ? 'on' : ''}.
  • Inline styles + CSS variables — colored values use inline style; components set a --card-color custom property so the CSS can reference it:
<div className="plot-toolbar" style={{ '--card-color': titleColor }} />