Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"rehype-highlight": "^7.0.2",
"rehype-slug": "^6.0.0",
"remark-gfm": "^4.0.1",
"solid-js": "^1.9.7",
"solid-js": "2.0.0-experimental.16",
"typescript": "^5.8.3",
"vinxi": "^0.5.7",
"vite": "^6.3.5",
Expand Down
128 changes: 74 additions & 54 deletions packages/timer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,110 +10,130 @@

Timer primitives related to [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) and [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout):

- [`makeTimer`](#maketimer) - Makes an automatically cleaned up timer.
- [`createTimer`](#createtimer) - [makeTimer](#maketimer), but with a fully reactive delay
- [`createTimeoutLoop`](#createtimeoutloop) - Like createInterval, except the delay only updates between executions.
- [`createPolled`](#createpolled) - Polls a function periodically. Returns an to the latest polled value.
- [`createIntervalCounter`](#createintervalcounter) - Creates a counter which increments periodically.
- [`makeTimer`](#maketimer) - Creates a timer and returns a function to clear it. No reactive lifecycle — the caller decides cleanup.
- [`createTimer`](#createtimer) - Reactive timer with an optionally reactive delay and automatic cleanup.
- [`createTimeoutLoop`](#createtimeoutloop) - Like `createTimer` with `setInterval`, but the delay only updates between executions.
- [`createPolled`](#createpolled) - Polls a function periodically and returns an accessor to its latest value.
- [`createIntervalCounter`](#createintervalcounter) - A counter that increments periodically.

## Installation

```bash
npm install @solid-primitives/timer
# or
yarn add @solid-primitives/timer
pnpm add @solid-primitives/timer
```

## How to use it

### Basic Usage
### makeTimer

#### makeTimer
Creates a timer ([setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) or [setInterval](https://developer.mozilla.org/en-US/docs/Web/API/setInterval)) and returns a function to clear it. This is a base primitive with no reactive lifecycle integration — the caller is responsible for cleanup.

Makes a timer ([setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) or [setInterval](https://developer.mozilla.org/en-US/docs/Web/API/setInterval)), automatically cleaning up when the current reactive scope is disposed.
To tie cleanup to a reactive scope, pass the returned function to `onCleanup`:

```ts
const callback = () => {};
const disposeTimeout = makeTimer(callback, 1000, setTimeout);
const disposeInterval = makeTimer(callback, 1000, setInterval);
import { onCleanup } from "solid-js";
import { makeTimer } from "@solid-primitives/timer";

// Manual cleanup
const clear = makeTimer(() => console.log("tick"), 1000, setInterval);
// ...
dispose(); // clean up manually if needed
clear();

// Tied to a reactive scope
onCleanup(makeTimer(() => console.log("tick"), 1000, setInterval));
```

#### createTimer
### createTimer

A reactive timer whose delay can be a static number or a reactive accessor. When the delay accessor returns `false`, the timer is disabled. Automatically cleans up when the reactive scope is disposed.

[makeTimer](#maketimer), but with a fully reactive delay. The delay can also be `false`, in which case the timer is disabled. Does not return a dispose function.
When the delay changes, the elapsed fraction of the previous delay carries forward — so changing from 1000ms to 2000ms after 250ms elapsed will fire the next callback after 1500ms, not 2000ms.

```ts
const callback = () => {};
createTimer(callback, 1000, setTimeout);
createTimer(callback, 1000, setInterval);
// with reactive delay
const callback = () => {};
import { createSignal } from "solid-js";
import { createTimer } from "@solid-primitives/timer";

// Static delay
createTimer(() => console.log("timeout"), 1000, setTimeout);
createTimer(() => console.log("interval"), 1000, setInterval);

// Reactive delay with pause support
const [paused, setPaused] = createSignal(false);
const [delay, setDelay] = createSignal(1000);
createTimer(callback, () => !paused() && delay(), setTimeout);
createTimer(callback, () => !paused() && delay(), setInterval);
// ...
setDelay(500);
// pause
setPaused(true);
// unpause
setPaused(false);

createTimer(
() => console.log("tick"),
() => !paused() && delay(),
setInterval,
);

setDelay(500); // change interval
setPaused(true); // pause
setPaused(false); // resume
```

#### createTimeoutLoop
### createTimeoutLoop

Similar to an interval created with [createTimer](#createtimer), but the delay does not update until the callback is executed.
Similar to `createTimer` with `setInterval`, but the delay is only read between executions rather than reactively. This means a delay change takes effect after the current interval completes.

```ts
const callback = () => {};
createTimeoutLoop(callback, 1000);
// with reactive delay
const callback = () => {};
import { createSignal } from "solid-js";
import { createTimeoutLoop } from "@solid-primitives/timer";

// Static delay
createTimeoutLoop(() => console.log("loop"), 1000);

// Reactive delay — updates between executions
const [delay, setDelay] = createSignal(1000);
createTimeoutLoop(callback, delay);
createTimeoutLoop(() => console.log("loop"), delay);
// ...
setDelay(500);
setDelay(500); // takes effect after the current interval fires
```

#### createPolled
### createPolled

Periodically polls a function, returning an accessor to its last return value.
Polls a function periodically and returns an accessor to its latest return value. The function is called immediately on creation, then after each interval. If the polling function reads reactive signals, the accessor also updates when those signals change.

```tsx
import { createSignal } from "solid-js";
import { createPolled } from "@solid-primitives/timer";

// Poll current time every second
const date = createPolled(() => new Date(), 1000);
// ...
<span>The time is: {date()}</span>;
// with reactive delay

// Reactive source — updates on interval AND when source changes
const [id, setId] = createSignal(1);
const user = createPolled(() => cache.get(id()), 5000);

// Reactive delay
const [delay, setDelay] = createSignal(1000);
createPolled(() => new Date(), delay);
// ...
const polled = createPolled(() => new Date(), delay);
setDelay(500);
```

#### createIntervalCounter
### createIntervalCounter

A counter which increments periodically based on the delay.
A counter that starts at `0` and increments by one each interval. Accepts a static or reactive delay.

```tsx
import { createSignal } from "solid-js";
import { createIntervalCounter } from "@solid-primitives/timer";

const count = createIntervalCounter(1000);
// ...
<span>Count: {count()}</span>;
// with reactive delay

// Reactive delay
const [delay, setDelay] = createSignal(1000);
createIntervalCounter(delay);
// ...
const count = createIntervalCounter(delay);
setDelay(500);
```

### Note on Reactive Delays

When a delay is changed, the fraction of the existing delay already elapsed be carried forward to the new delay. For instance, a delay of 1000ms changed to 2000ms after 250ms will be considered 1/4 done, and next callback will be executed after 250ms + 1500ms. Afterwards, the new delay will be used.

## Demo
## Note on Reactive Delays

You may view a working example here: https://codesandbox.io/s/solid-primitives-timer-6n7dt?file=/src/index.tsx
When a delay changes, the fraction of the current interval already elapsed carries over to the new delay. For example, if a 1000ms delay is changed to 2000ms after 250ms (25% done), the first callback with the new delay fires after 1500ms (75% of 2000ms), and subsequent callbacks fire every 2000ms.

## Changelog

Expand Down
6 changes: 4 additions & 2 deletions packages/timer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,12 @@
"primitives"
],
"peerDependencies": {
"solid-js": "^1.6.12"
"@solidjs/web": "2.0.0-beta.7",
"solid-js": "2.0.0-beta.7"
},
"typesVersions": {},
"devDependencies": {
"solid-js": "^1.9.7"
"@solidjs/web": "2.0.0-beta.7",
"solid-js": "2.0.0-beta.7"
}
}
Loading
Loading