useBountyStatus accepts onStatusChange and fallbackBounty and then ignores both: the status-change callback is never invoked by any code path
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 72/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- react, typescript
- Domain
- frontend
Research direction
Start by reading src/hooks/useBountyStatus.ts and the shared registry in src/hooks/bountyPoller; check the hook's consumers to understand how they use both options. Implement the intended callback and fallback behavior, making the first status notification decision explicit and keeping the reported source accurate. Done when tests cover status transitions and the initial fallback state, and the hook no longer silently ignores either option.
Written by the indexing model from the issue text.
Description
Location
src/hooks/useBountyStatus.ts:16-22 (the UseBountyStatusOptions interface) and :35-48 (the hook body).
Problem
Both options are destructured and never used again:
export function useBountyStatus({
bountyId,
fallbackBounty, // never referenced below
interval = 5000,
enabled = true,
onStatusChange, // never referenced below
}: UseBountyStatusOptions): UseBountyStatusResult {
Search the file: after that destructure, neither identifier appears again. The body is a useSyncExternalStore over the module-level registry in ./bountyPoller, and that registry has no notion of either option — subscribeBounty(id, listener, interval) takes no callback and no fallback, and notify() only invokes the store-change listeners React registered.
So:
onStatusChangeis a dead callback. A consumer that passes it (to refresh the page when a bounty flips toclaimed, to re-render a payout panel when it flips topaid, to stop offering a Claim button) is silently never called, for any transition, ever. There is no runtime error and no type error — the option is declared, so it typechecks and reads as supported.fallbackBountyis a dead input. The hook'sUseBountyStatusResult.bountycomes solely fromgetBountySnapshot(bountyId).bounty, which isnulluntil the first successful poll, so a caller that already has a server-rendered bounty and passes it asfallbackBountystill getsbounty: nullon first paint and renders an empty/loading state for a bounty it already has in hand.
This looks like fallout from the #631 rewrite that replaced the hook's internal useSmartPolling loop with the shared registry: the options survived the rewrite, their implementations did not. (Note this is the opposite of the previously reported behaviour where onStatusChange fired with a wrong value — on the current default branch it does not fire at all, and fallbackBounty is not substituted at all. Worth reconciling the two when triaging.)
Why it matters
The failure mode is a silent no-op on the bounty detail screen: whatever a consumer wired to onStatusChange simply does not happen. Because the option is accepted, nothing — not the compiler, not a lint rule, not a runtime warning — tells the author their callback is dropped. The status itself is still returned, so a consumer can poll status in its own useEffect instead, which means the bug reads as "my callback never ran" rather than "the data is wrong", and is easy to misattribute to the poller.
Reproduction
const { status } = useBountyStatus({
bountyId: "b1",
interval: 1000,
onStatusChange: (s) => console.log("changed to", s),
});
- Render this against a backend where bounty
b1isopen. - Claim
b1from another session so the backend status becomesclaimed. statusreturned by the hook updates to"claimed"on the next tick, but nothing is ever logged.
Suggested fix
Either implement the options or remove them from the public type — do not leave them declared and unimplemented.
To implement onStatusChange, derive the transition in the hook rather than in the shared registry (each subscriber wants its own callback, and the registry is shared):
const prevStatusRef = useRef<BountyStatus | null>(null);
const status = snapshot.bounty?.status ?? null;
useEffect(() => {
if (status !== null && status !== prevStatusRef.current) {
const first = prevStatusRef.current === null;
prevStatusRef.current = status;
if (!first) onStatusChange?.(status); // or fire on first observation, document which
}
}, [status, onStatusChange]);
Note the first-observation decision needs to be explicit and documented: firing on the initial load is what produced spurious notifications before.
For fallbackBounty, return it when the registry has nothing yet (bounty: snapshot.bounty ?? fallbackBounty ?? null) and keep source honest by reporting a distinct value (or null) for that case, so a consumer can still tell server-rendered-fallback from polled-live.
- Dominant language
- TypeScript
- Stars
- 1
- Forks
- 94
- Avg merge
- 1h 2m
- Merged PRs (30d)
- 25
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from MergeFi/frontend
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
bug testing
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
accessibility bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
MergeFi/frontend#835 · 1 comment ·
Maintainers usually reply within 1 day
All issues in MergeFi/frontend
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
zapcooking/frontend#946 ·
Maintainers usually reply within 1 day
-
enhancement good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
WiliamRosa/cosmos-semantic-platform#40 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
liberation-data/drivine#733 · 1 comment ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
makecindy/cindy#10062 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day