All files / src/lib useDeviceValue.ts

75% Statements 3/4
100% Branches 0/0
66.66% Functions 2/3
100% Lines 3/3

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 287x                                     8x           29x    
"use client";
 
import { useSyncExternalStore } from "react";
 
// A device-local fact (localStorage, or an attribute a pre-hydration script set on
// <html>) cannot be read while rendering on the server, so it can't seed useState:
// the first client render must still match the server's HTML. The house pattern used
// to be "seed a placeholder, then setState in a mount effect" — a cascading render
// React now lints against (react-hooks/set-state-in-effect).
//
// useSyncExternalStore is the sanctioned read: SSR and the hydration render get
// `serverValue`, React re-reads `read()` immediately after mount and re-renders if it
// differs — exactly the two-phase result the mount effect produced. `read` must return
// a primitive (a fresh object every call would loop).
//
// Nothing outside the tab mutates these values mid-life, so there is nothing to
// subscribe to; a component that also WRITES the value keeps its own state override
// (see RoundSurface's chosenBoxMs / RoundLengthDialog's onCommit wiring), which is
// what preserves the choice when the storage write silently fails (private mode).
const noSubscribe = () => () => {};
 
export function useDeviceValue<T extends string | number | boolean | null>(
  read: () => T,
  serverValue: T,
): T {
  return useSyncExternalStore(noSubscribe, read, () => serverValue);
}