Skip to content

upgrade: timer package upgrade for Solid 2.0#843

Merged
davedbase merged 10 commits into
solidjs-community:nextfrom
davedbase:update/v2/timer
May 25, 2026
Merged

upgrade: timer package upgrade for Solid 2.0#843
davedbase merged 10 commits into
solidjs-community:nextfrom
davedbase:update/v2/timer

Conversation

@davedbase

@davedbase davedbase commented Apr 18, 2026

Copy link
Copy Markdown
Member

Reimplements the timer package for solid-js@2.0.0.

  • makeTimer no longer auto-registers with onCleanup — it returns a cleanup function and lets the caller decide. createTimer and createTimeoutLoop call onCleanup(makeTimer(...)) at their own call sites. This aligns with the make* / create* naming convention used across solid-primitives.
  • isServer is now imported from @solidjs/web (moved out of solid-js/web in 2.0).
  • createEffect updated to the split compute/apply model: createEffect(compute, apply). The apply phase returns a cleanup function instead of using onCleanup (which doesn't work in the apply phase).
  • batch removed — signal writes are auto-batched via microtask in 2.0. Tests use flush() where synchronous commitment is needed.
  • The reconciliation logic in createTimer (fractional delay carry-over) is rewritten to manage the full timer lifecycle directly in the apply cleanup closure, replacing the 1.x signal self-trigger trick.

Bug fix: createPolled — issue #808

The original implementation used createMemo(() => createSignal(fn(value), options)) and called memo()[1](fn) from the timer. In production builds memo()[1] was undefined, crashing on every timer tick.

The fix captures the signal setter in a closure variable that is updated each time the memo re-runs:

let set!: Setter<T>;
const memo = createMemo(() => {
  const [get, _set] = createSignal<T>(fn(value) as Exclude<T, Function>, options);
  set = _set;
  return get;
});
untrack(memo); // force initial evaluation so `set` is defined before the timer fires
createTimer(() => set(prev => fn(prev)), timeout, setInterval);
return () => memo()();

I'm not fully confident in this createPolled fix and would welcome a second look. The main concern is whether capturing set in a closure and updating it on each memo re-run (dep change) is the right pattern in Solid 2.0, or whether there's a cleaner approach using the new writable derived signal (createSignal(computeFn, initialValue, options)) that avoids the TypeScript overload discrimination issues we ran into.

Summary by CodeRabbit

  • Documentation

    • Rewrote README with improved explanations, examples, and clearer behavior clarifications for all timer primitives.
  • Chores

    • Updated peer dependencies to Solid 2.0.0-beta.10.

Review Change Stack

@changeset-bot

changeset-bot Bot commented Apr 18, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 4a8351f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@davedbase davedbase marked this pull request as draft April 20, 2026 02:14
@davedbase davedbase changed the base branch from main to next April 21, 2026 01:47
@davedbase davedbase added this to the Solid 2.0 Upgrade milestone Apr 21, 2026
@davedbase davedbase marked this pull request as ready for review May 17, 2026 13:29
@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 30ff5f1a-8a65-4ac4-9150-a13fca8db4ec

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/timer/src/index.ts`:
- Around line 137-143: The effect in createTimeoutLoop exits early when
currTimeout === false so it stops watching timeout() and won’t restart when
timeout() later returns a number; instead, change the effect to always subscribe
to the timeout accessor (call timeout() at top to register) and conditionally
set/clear the interval: if currTimeout === false, return a cleanup that does
nothing but keeps the effect reactive; otherwise create the interval (using
setInterval and clearInterval) and ensure setCurrentTimeout(timeout()) is still
invoked inside handler; reference createTimeoutLoop, currTimeout, timeout(),
handler and setCurrentTimeout when making this change so the effect continues to
react to transitions from false -> number.
- Around line 88-113: The reconciliation path can cause the handler to be
scheduled twice because callHandler may set done=true inside the setTimeout
branch but subsequent code still starts a new timer; fix by guarding timer
creation with the done flag: (1) inside the reconcile setTimeout callback, only
assign mainId = timer(callHandler, currDelay) if done is false (ensure the
existing if (!done) check is present and correct around mainId = timer), and (2)
before creating the fallback id = timer(callHandler, currDelay) at the end of
the function, check if done is true and if so return a no-op cleanup instead of
starting a new timer. Reference symbols: callHandler, done, mainId, reconcileId,
id, timer, fractionDone, shouldHandleFraction.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4151e0a2-401a-4e60-9749-c5a461837749

📥 Commits

Reviewing files that changed from the base of the PR and between 76d6f2e and 9150b83.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • packages/timer/README.md
  • packages/timer/package.json
  • packages/timer/src/index.ts
  • packages/timer/test/index.test.ts

Comment thread packages/timer/src/index.ts
Comment thread packages/timer/src/index.ts Outdated
@davedbase davedbase merged commit 724753d into solidjs-community:next May 25, 2026
1 check passed
@davedbase davedbase deleted the update/v2/timer branch May 25, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant