useSmartPolling stops polling after the first re-render because useBountyStatus passes inline fetchFn/compareFn/onDataChange

Aperta
#568 6 commenti 0 reazioni 1 assegnatario Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@Biglamar ci sta già lavorando.

Dal 27/9/2026.

  • #577 di @Biglamar — aperta
  • #808 di @Biglamar — aperta

Valutazione

Questa issue non è ancora stata valutata.

Descrizione

bug performance Stellar Wave very hard

Problem

src/hooks/useSmartPolling.ts's main polling effect is keyed on [enabled, fetchData], and fetchData is a useCallback over [fetchFn, compareFn, interval, backoffMultiplier, maxBackoff, unchangedThreshold, onDataChange]. The effect only ever creates the interval once, guarded by hasInitialized:

useEffect(() => {
  isMountedRef.current = true;
  if (!enabled) return;
  if (!hasInitialized.current) {
    hasInitialized.current = true;
    dispatch({ type: 'START_POLLING' });
    fetchData();
    intervalRef.current = setInterval(fetchData, currentIntervalRef.current);
  }
  return () => {
    isMountedRef.current = false;
    if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; }
  };
}, [enabled, fetchData]);

src/hooks/useBountyStatus.ts, the only consumer, passes fetchFn, compareFn and onDataChange as inline arrow functions, so they get a new identity on every render. ClaimButton.tsx's own comment already acknowledges that useBountyStatus "rebuilds its fetchFn / compareFn / onDataChange inline on every render".

What actually happens

  1. Mount: the effect runs, calls fetchData(), and starts setInterval.
  2. The first fetch resolves: setData and setIsLoading(false) trigger a re-render, so there's a new fetchData identity.
  3. The effect's cleanup runs: clearInterval(...) and isMountedRef.current = false.
  4. The effect body re-runs: isMountedRef.current = true, but hasInitialized.current is already true, so no new interval is created.

From then on, nothing polls until the tab is hidden and shown again, because the separate visibilitychange effect restarts the interval. Meanwhile:

  • isPolling stays true, since STOP_POLLING is deliberately not dispatched in the cleanup.
  • BountyStatus keeps showing the pulsing green "live" dot.
  • ClaimButton's 2s status refresh used for claim-race detection (#46) never fires.

Suggested fix

  • Keep fetchFn, compareFn and onDataChange in refs, updated every render, so fetchData is identity-stable.
  • And/or drop the hasInitialized guard so the effect re-creates the interval whenever it re-runs.
  • Add a test: render a component using useBountyStatus, advance timers past several intervals, and assert fetchBounty was called more than once.

Related: #46 (introduced smart polling), #359 (test coverage for these hooks), #361 (isMountedRef guard).

Lingua principale
TypeScript
Stelle
1
Fork
94
Merge medio
1h 2m
PR unite (30g)
25

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di MergeFi/frontend

Tutte le issue di MergeFi/frontend

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.