Skip to content

feat: allow deriveds to reference their own values#17666

Open
Rich-Harris wants to merge 4 commits intomainfrom
allow-recursive-derived
Open

feat: allow deriveds to reference their own values#17666
Rich-Harris wants to merge 4 commits intomainfrom
allow-recursive-derived

Conversation

@Rich-Harris
Copy link
Copy Markdown
Member

#17646 got me thinking: why don't we allow deriveds to reference their own values? It's unusual for it to be useful, but there are arguably valid use cases, at least now that deriveds are writable — things like this:

<script>
	var score = $state(0);
	var highscore = $derived(Math.max(score, highscore || 0));
</script>

<p>score: {score}</p>
<p>highscore: {highscore}</p>

<button onclick={() => score += 1}>score a point</button>
<button onclick={() => score = 0}>start new game</button>
<button onclick={() => score = highscore = 0}>reset</button>

That works on this branch, though one test is failing for reasons I don't really understand. Won't invest any time into figuring it out unless we decide that this does in fact make sense.

Before submitting the PR, please make sure you do the following

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • Prefix your PR title with feat:, fix:, chore:, or docs:.
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.
  • If this PR changes code within packages/svelte/src, add a changeset (npx changeset).

Tests and linting

  • Run the tests with pnpm test and lint the project with pnpm lint

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Feb 10, 2026

🦋 Changeset detected

Latest commit: c32b293

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
svelte Minor

Not sure what this means? Click here to learn what changesets are.

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

@Rich-Harris Rich-Harris temporarily deployed to Publish pkg.pr.new (maintainers) February 10, 2026 02:34 — with GitHub Actions Inactive
@github-actions
Copy link
Copy Markdown
Contributor

Playground

pnpm add https://pkg.pr.new/svelte@17666

@dominikg
Copy link
Copy Markdown
Contributor

How different is this from using an effect?

var score = $state(0);
var highscore = $derived(Math.max(score, highscore || 0));
var score = $state(0);
var highscore = $state(0);
$effect(()=>{
  if(score>highscore) {
    highscore=score;
  }
})

This feels like a small optimization for a rare case and i wouldn't want to give up any safety for the general case just for this.

@7nik
Copy link
Copy Markdown
Contributor

7nik commented Feb 10, 2026

The effect variant updates only in the next tick, while derived can update right away if there is a demand.

I more like #13250 design - it feels more natural

var score = $state(0);
var highscore = $derived.by((highscore = 0) => Math.max(score, highscore));

Though I guess both will confuse TS.

@dummdidumm
Copy link
Copy Markdown
Member

TS confusion is a good point - with $derived.by you can ensure it is typeable correctly but with $derived you'll get the "used before being assigned" error which you can only silene through var (ugh) or // @ts-expect-error (will swallow other errors on the same line)

@Rich-Harris
Copy link
Copy Markdown
Member Author

What I like about this approach over #13250 is that there's no new surface area — nothing to learn, it Just Works. TypeScript will complain about use before assignment but as you say there are two options to solve it ($derived.by or var) whereas with #13250 you don't have a choice, you have to use $derived.by. Personally I would use var for something like this.

There may be other arguments in favour of the explicit previous value that I'm not considering

@dummdidumm
Copy link
Copy Markdown
Member

nothing to learn, it Just Works

That is only partially true because realistically you're hit with the TS error right away, wondering "am I doing this right even?" and then you gotta learn about var and accept the quirkyness

@Rich-Harris Rich-Harris temporarily deployed to Publish pkg.pr.new (maintainers) February 10, 2026 15:57 — with GitHub Actions Inactive
@Rich-Harris
Copy link
Copy Markdown
Member Author

Here's something you can do with this approach that I'm fairly sure you can't do with an explicit previous value:

<script>
	var c = $derived(Math.round(((f || 32) - 32) * 5 / 9));
	var f = $derived(Math.round(((c || 0) * 9 / 5) + 32));
</script>

<label>
	<input type="number" bind:value={c}>
	temperature in celsius
</label>

<label>
	<input type="number" bind:value={f}>
	temperature in fahrenheit
</label>

@Rich-Harris Rich-Harris marked this pull request as ready for review February 13, 2026 21:26
@mauriziocescon
Copy link
Copy Markdown

mauriziocescon commented Feb 21, 2026

Just my 2 cents: angular has an explicit split between the source ('score' in the initial example) and the executed func.

And provides both previous values: source and derivation.

It's more verbose, but it's also less frequent to use and pretty powerful.

Overall, IMHO it works really well.

https://angular.dev/guide/signals/linked-signal#accounting-for-previous-state

PS: relevant discussion as well 'angular/angular#59580'

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.

5 participants