|
1 | 1 | import { useEffect, useRef, useState } from 'react'; |
2 | 2 |
|
3 | 3 | /** |
4 | | - * Coalesce a fast-changing value so React consumers only see updates: |
5 | | - * - at most once per animation frame (RAF), and |
6 | | - * - optionally no more often than `minIntervalMs`. |
| 4 | + * A utility hook that coalesces a fast changing value to the display’s frame rate. |
| 5 | + * It accepts any “noisy” input (arrays, objects, numbers, etc.) and exposes a value |
| 6 | + * that React consumers will see at most once per animation frame (via |
| 7 | + * `requestAnimationFrame`). This is useful when upstream sources (selectors, sockets, |
| 8 | + * DB listeners) can fire multiple times within a single paint and you want to avoid |
| 9 | + * extra renders and layout churn. |
| 10 | + * |
| 11 | + * How it works: |
| 12 | + * - Keeps track of the latest incoming value |
| 13 | + * - Ensures there is **at most one** pending RAF at a time |
| 14 | + * - When the RAF fires, commits the **latest** value to state (`emitted`) |
| 15 | + * - If additional changes arrive before the RAF runs, they are merged (the last write |
| 16 | + * operation wins) and no new RAF is scheduled |
| 17 | + * |
| 18 | + * With this hook you can: |
| 19 | + * - Feed a `FlatList`/`SectionList` from fast changing sources without spamming re-renders |
| 20 | + * - Align React updates to the paint cadence (one publish per frame) |
| 21 | + * - Help preserve item anchoring logic (e.g., MVCP) by reducing in-frame updates |
| 22 | + * |
| 23 | + * **Caveats:** |
| 24 | + * - This hook intentionally skips intermediate states that occur within the same |
| 25 | + * frame. If you must observe every transition (e.g., for analytics/reducers), do that |
| 26 | + * upstream; this hook is for visual coalescing |
| 27 | + * - Equality checks are simple referential equalities. If your producer recreates arrays |
| 28 | + * or objects each time, you’ll still publish once per frame. To avoid even those |
| 29 | + * emissions, stabilize upstream |
| 30 | + * - This is not a silver bullet for throttle/debounce; it uses the screen’s refresh cycle; |
| 31 | + * If you need “no more than once per X ms”, layer that upstream |
| 32 | + * |
| 33 | + * Usage tips: |
| 34 | + * - Prefer passing already-memoized values when possible (e.g., stable arrays by ID). |
| 35 | + * - Pair with a stable `keyExtractor` in lists so coalesced updates map cleanly to rows. |
| 36 | + * - Do not cancel/reschedule on prop changes; cancellation is handled on unmount only. |
| 37 | + * |
| 38 | + * @param value The upstream value that may change multiple times within a single frame. |
| 39 | + * @returns A value that updates **at most once per frame** with the latest input. |
7 | 40 | */ |
8 | 41 | export const useRafCoalescedValue = <S>(value: S): S => { |
9 | 42 | const [emitted, setEmitted] = useState<S>(value); |
|
0 commit comments