[2.0] Effects re-run (and run cleanup) when the compute returns an equal value; there's no `equals` option
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 54/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- typescript
- Domain
- backend-api-design
Research direction
Start with packages/signals/src/signals.ts and inspect EffectOptions, createEffect, and createRenderEffect; the report says an equals option is currently ignored and gives a signals-only reproduction. Run that reproduction or add a focused test for repeated equal compute results, including cleanup counts. Done means the chosen behavior is implemented and verified, or the reactivity documentation clearly explains the current behavior and memo workaround.
Written by the indexing model from the issue text.
Description
Describe the bug
createEffect(compute, effectFn) and createRenderEffect re-run the effect function, and the previous cleanup, every time a tracked read changes, even when compute returns the same value as before. EffectOptions (packages/signals/src/signals.ts) has no equals, and passing one is silently ignored.
Your Example Website or App
Minimal repro inline below (signals-only). Real-world occurrence: https://github.com/openclaw/openclaw/blob/1989e3dc436/.agents/skills/solid/references/lessons.md#L8
Steps to Reproduce the Bug or Issue
import { createSignal, createRoot, createEffect, createMemo, flush } from "@solidjs/signals";
let applies = 0, cleanups = 0;
const [n, setN] = createSignal(1);
createRoot(() => createEffect(() => n() > 0, () => { applies++; return () => { cleanups++; }; }));
flush(); setN(2); flush(); setN(3); flush();
// compute returned true three times
| Setup | computes | applies | cleanups |
|---|---|---|---|
| Plain | 3 | 3 | 2 |
createMemo(() => n() > 0) in front |
1 | 0 | |
{ equals: (a, b) => a === b } passed as options |
3 | (ignored) |
createRenderEffect gives the same numbers.
Expected behavior
Either:
- the effect skips its apply and cleanup when the compute result is equal (using the same default
equalsas memos, or anequalsoption on effects); or - the reactivity docs and cheatsheet state the current behavior clearly and recommend a memo in front. An unknown
equalsoption could also warn in dev instead of being silently ignored.
Screenshots or Videos
No response
Platform
- OS: macOS (Darwin 25.5)
- Browser: n/a (reproduced in Node v24.18.0 with vitest, importing source)
- Version: solid
next@ b0ad1df50 (2026-10-20, after 2.0.0-rc.16)
Additional context
- React developers assume the opposite:
useEffectskips when its dependencies are unchanged. - Cleanups churn: because cleanups re-run, subscriptions and timers are torn down and restarted on unrelated changes.
- Seen in production: OpenClaw hit this in production. A props refresh re-ran an effect with an unchanged status, which cancelled a timer and never restarted it (https://github.com/openclaw/openclaw/blob/1989e3dc436/.agents/skills/solid/references/lessons.md#L8).
- Documented as a pitfall: the From React additions (solidjs/solid-docs#1664) describe this.
- Dominant language
- TypeScript
- Stars
- 36.1k
- Forks
- 1.1k
- Avg merge
- 11h 42m
- Merged PRs (30d)
- 350
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 solidjs/solid
-
`lazy()`: remounting an already-loaded component renders empty (or the Suspense fallback) since 1.9.14Possibly taken @itisrmk claimed this 3 days ago. Open1.x
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 48/100
Maintainers usually reply within 1 day
-
[2.0] The MIGRATION.md replacement for `from()` keeps the upstream subscription after dispose; add a subscription helperPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 5/5 Over a week Newbie friendliness 35/100
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 56/100
Maintainers usually reply within 1 day
-
[compiler] A valueless `prop:` attribute is dropped silently by both compilersPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 4/5 3-5 days Newbie friendliness 45/100
Maintainers usually reply within 1 day
Similar issues
-
Area/Cloud Needs Triage Type/Bug
Difficulty 1/5 1-3 hours Newbie friendliness 86/100
wso2/product-integrator#2847 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 67/100
bluesky-social/social-app#12118 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 1/5 Under an hour Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
heygen-com/hyperframes#6021 ·
Maintainers usually reply within 1 day
-
check:passed streams:edit
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day