Skip to content

Comments

fix(useRafFn): resolve reactive null fpsLimit not being handled#5284

Merged
43081j merged 3 commits intovueuse:mainfrom
nemanjamalesija:fix/useRafFn-reactive-null-fpsLimit
Feb 6, 2026
Merged

fix(useRafFn): resolve reactive null fpsLimit not being handled#5284
43081j merged 3 commits intovueuse:mainfrom
nemanjamalesija:fix/useRafFn-reactive-null-fpsLimit

Conversation

@nemanjamalesija
Copy link
Contributor

Fixes #5273

When you pass fpsLimit as a ref(null) (so you can toggle the limit on/off later), the callback never fires.

The problem is that the truthiness check is on the ref object itself, which is always truthy — so toValue() resolves to null and 1000 / null gives Infinity. The interval limit is never satisfied and the loop just skips every frame.

The fix is simple — unwrap with toValue() first, then check:

// before
return fpsLimit ? 1000 / toValue(fpsLimit) : null

// after
const limit = toValue(fpsLimit)
return limit ? 1000 / limit : null

Also added a test for this case. All 12 tests passing.

When fpsLimit is passed as a ref(null), the truthiness check evaluates
the ref object itself (always truthy) rather than the unwrapped value.
This causes 1000 / null = Infinity, so the interval limit is never
satisfied and the callback never fires.

Unwrap fpsLimit with toValue() before the truthiness check so that
reactive null/0/false values correctly resolve to no fps limit.

Closes vueuse#5273
@dosubot dosubot bot added the size:S This PR changes 10-29 lines, ignoring generated files. label Feb 5, 2026
@OrbisK
Copy link
Member

OrbisK commented Feb 5, 2026

Thank you for your PR 🙏🏽!

Can you briefly explain why we should handle ref(null) if the types only accepts MaybeRef<number>?

@9romise
Copy link
Member

9romise commented Feb 6, 2026

Can you briefly explain why we should handle ref(null) if the types only accepts MaybeRef<number>?

Oh, I confirmed this as a bug in #5273. I hadn't noticed the type restriction before.🤦‍♂️

But if someone turns off strictNullChecks in their tsconfig.json, TypeScript won't report an error. For example:

const fpsLimit = ref<number>(null)

useRafFn(() => {

}, { fpsLimit })

@nemanjamalesija
Copy link
Contributor Author

You're right that the types only accept MaybeRefOrGetter<number>, but I think that's actually the root of the problem. The option defaults to undefined (no limit), so the API already supports the concept of "no limit." But if a user wants to reactively toggle between limited and unlimited at runtime, they can't express that with the current type.

As @9romise mentioned, this also surfaces with strictNullChecks off, but beyond that I think the type should be widened to MaybeRefOrGetter<number | null | undefined> to officially support the reactive toggle use case. I'll update the PR with that change.

@OrbisK
Copy link
Member

OrbisK commented Feb 6, 2026

As @9romise mentioned, this also surfaces with strictNullChecks off, but beyond that I think the type should be widened to MaybeRefOrGetter<number | null | undefined> to officially support the reactive toggle use case. I'll update the PR with that change.

Honestly, I don't think we should allow MaybeRefOrGetter<null | undefined>. You should always handle what you expect null to be in your code. I think @43081j might have a stronger opinion on that 😅

@nemanjamalesija
Copy link
Contributor Author

nemanjamalesija commented Feb 6, 2026

That's a fair point.

That said, I think the runtime fix itself still stands on its own. It's not really about supporting null, it's about fixing the toValue() ordering. When fpsLimit was made reactive in #4409, the truthiness check stayed on the raw fpsLimit parameter instead of the unwrapped value. Since a ref object is always truthy, toValue() then resolves to whatever the ref holds, and 1000 / null gives Infinity (silently breaking the loop with no errors).

The fix just moves toValue(), before the truthiness check.

@9romise
Copy link
Member

9romise commented Feb 6, 2026

From my perspective, the core need here seems to be the ability to control the limit reactively (enable/disable).
See: https://github.com/vueuse/vueuse/blob/main/packages%2Fcore%2FuseRafFn%2Findex.ts#L27-L33

However, while we treat undefined as no limit, the type is currently restricted to MaybeRefOrGetter<number>, which makes it impossible to support this use case.

@43081j
Copy link
Collaborator

43081j commented Feb 6, 2026

change makes sense to me but i agree, i would be explicit. is null "off", or is undefined "off"? we should pick one and only accept that. in this case i'd lean towards null since it implies someone had to actively set the value, rather than just mistakenly passing an undefined variable.

note that this is just a typescript decision, the underlying code outside of TS will handle null and undefined still

@9romise
Copy link
Member

9romise commented Feb 6, 2026

change makes sense to me but i agree, i would be explicit. is null "off", or is undefined "off"? we should pick one and only accept that. in this case i'd lean towards null since it implies someone had to actively set the value, rather than just mistakenly passing an undefined variable.

note that this is just a typescript decision, the underlying code outside of TS will handle null and undefined still

I'll cast my vote for null, for the same reason.

@nemanjamalesija nemanjamalesija force-pushed the fix/useRafFn-reactive-null-fpsLimit branch from 215601c to 059d159 Compare February 6, 2026 10:59
43081j
43081j previously approved these changes Feb 6, 2026
@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Feb 6, 2026
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 6, 2026

Open in StackBlitz

@vueuse/components

npm i https://pkg.pr.new/@vueuse/components@5284

@vueuse/core

npm i https://pkg.pr.new/@vueuse/core@5284

@vueuse/electron

npm i https://pkg.pr.new/@vueuse/electron@5284

@vueuse/firebase

npm i https://pkg.pr.new/@vueuse/firebase@5284

@vueuse/integrations

npm i https://pkg.pr.new/@vueuse/integrations@5284

@vueuse/math

npm i https://pkg.pr.new/@vueuse/math@5284

@vueuse/metadata

npm i https://pkg.pr.new/@vueuse/metadata@5284

@vueuse/nuxt

npm i https://pkg.pr.new/@vueuse/nuxt@5284

@vueuse/router

npm i https://pkg.pr.new/@vueuse/router@5284

@vueuse/rxjs

npm i https://pkg.pr.new/@vueuse/rxjs@5284

@vueuse/shared

npm i https://pkg.pr.new/@vueuse/shared@5284

commit: 198eb95

@codecov
Copy link

codecov bot commented Feb 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 65.46%. Comparing base (39afd85) to head (198eb95).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5284      +/-   ##
==========================================
- Coverage   65.46%   65.46%   -0.01%     
==========================================
  Files         344      344              
  Lines        8039     8040       +1     
  Branches     2483     2471      -12     
==========================================
  Hits         5263     5263              
- Misses       2257     2258       +1     
  Partials      519      519              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@43081j 43081j added this pull request to the merge queue Feb 6, 2026
Merged via the queue into vueuse:main with commit 8ce0dae Feb 6, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG | useRafFn | Reactive value of null not handled for fpsLimit

4 participants