Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: add$state.invalidate rune#15673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
Ocean-OS wants to merge20 commits intosveltejs:main
base:main
Choose a base branch
Loading
fromOcean-OS:$state-invalidate

Conversation

Ocean-OS
Copy link
Contributor

@Ocean-OSOcean-OS commentedApr 3, 2025
edited
Loading

This adds the$state.invalidate rune for forcing updates on a variable declared with$state or$state.raw, based on$state.opaque and closely related to#14520 (comment). If you have used Svelte 3 or 4, this is the equivalent offoo = foo, which currently doesn't have a Svelte 5 equivalent.
Now, while I don't quite know the exact reasons that$state.opaque was rejected (the most info we have isthis), I have noticed some ergonomic issues, such as:

  • A variable that needs a force update has to have its declaration changed to a destructured pattern with$state.opaque like so:
//before:letthing=$state(newClass());//after:let[thing,invalidateThing]=$state.opaque(newClass());
  • There's no good way to name the invalidate function. With similar functions outside of Svelte like React'suseState, the common convention is[thing, setThing], but with$state.opaque, there wasn't a clear non-verbose way to name the invalidator.

Meanwhile,$state.invalidate can be used without these caveats on any$state or$state.raw variable:

letcounter=$state(newCounter());//imagine `Counter` as being a class from an NPM package or a native classfunctionincrement(){counter.increment();// counter's internal state has changed, but Svelte has no way of knowing that$state.invalidate(counter);}$effect(()=>console.log(`Count is${counter.count}`));

Closes#14520

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 withfeat:,fix:,chore:, ordocs:.
  • 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 withinpackages/svelte/src, add a changeset (npx changeset).

Tests and linting

  • Run the tests withpnpm test and lint the project withpnpm lint

ThePaSch, jjones315, mjadobson, bekriebel, aster-void, and MrVauxs reacted with thumbs up emojiMrVauxs reacted with eyes emoji
@changeset-botchangeset-bot
Copy link

changeset-botbot commentedApr 3, 2025
edited
Loading

🦋 Changeset detected

Latest commit:a0dfeea

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

This PR includes changesets to release 1 package
NameType
svelteMinor

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

@github-actionsGitHub Actions
Copy link
Contributor

Playground

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

@Thiagolino8
Copy link

Thiagolino8 commentedApr 3, 2025
edited
Loading

I prefer this version as it resembles existing APIs likehttps://vuejs.org/api/reactivity-advanced.html#triggerref
And then, unlike $state.opaque, this one can be used in class properties.

@gyzerok
Copy link
Contributor

gyzerok commentedApr 3, 2025
edited
Loading

We are now looking closely at how we can utilize writable deriveds to our benefit which have led up to the question on how to reset a derived back to its computed value. Which leads me to question if there should be also$derived.invalidate rune. What do you think?

@dummdidumm
Copy link
Member

What's the use case of reverting a value back to its computed value, and is this something you could write a wrapper for? Something like

letprev;letvalue=$derived.by(()=>{prev=value;// ...});// ...value=prev;

@gyzerok
Copy link
Contributor

gyzerok commentedApr 3, 2025
edited
Loading

@dummdidumm we are now trying to draft how we would write our models to support optimistic updates and seems like writable derived is exactly a perfect fit for it. We are building a real-time application and are looking into supporting offline-mode to a certain degree.

So imagine the same example that isdescribed in the docs with the slight change that we can schedule multiple optimistic updates before server responds. Now if the server responds with error on the first update what we want is to reset the value to its "confirmed" state and try to reapply second, third, etc optimistic updates again.

While the way you suggest to solve it would work it'll be kind of impractical when we have multiple models with tens of fields - it'd be simply a lot of code. The reasoning is sort of the same as with writable deriveds. While you can achieve writable deriveds with$state and$effect having them seems simpler at scale.

classModelextendsAbstractModel{// This is a confirmed value that is consistent with the server state.// Here I use only one field as an example, but lets imagine there are 10s of them,// and a single change could involve updating multiple fields.privateserverCounter=$state(0)// this value we will use for UI and will optimistically updatecounter=$derived(this.serverCounter)increment(amount:number):void{// I don't have an exact API here yet, we are still looking at how to best implement it.// However lets imagine we store this functions in some collection,// then we apply optimistic updates immediately, but run server queries in sequence.// If the request responds with an error, we reset all the deriveds on the model// and reapply remaining proposals again, then we send the second request and so on.// Here $derived.invalidate would come in handy.this.propose(()=>{this.count+=amountreturnasync()=>{this.serverCount=awaitfetch()}})}}// somewhere in UI clicks button multiple times and we call increment multiple timesmodel.increment(1)model.increment(3)model.increment(2)

The idea here is to have something conceptually similar to what is written inthis article, without patches and snapshots though. If you know of any better ways to do this in Svelte and have time/energy to share would also appreciate any ideas.

@dummdidumm
Copy link
Member

If you have a definite variable that contains the "real" state, and you have a way to know it errors (which you need anyway else how do you know how to call$derived.invalidate) why not dothis.counter = this.#serverCounter then?

@gyzerok
Copy link
Contributor

Yeah, you are right. Ideally we still hope that we can find a way to make a somewhat generic solution, so we could apply it to any model. Of course given the lack of decorators and ability to iterate over class properties in Svelte it might be not achievable, but lets see.

Anyway, thank you for looking into this. Perhaps it'd be better for me to open a separate issue with better example if we end up needing something like$derived.invalidate in the end :)

@gyzerok
Copy link
Contributor

After todays drafting session we were able to restructure our idea such that we can still keep it generic without the need to manually invalidate for$derived, however$state.invalidate would come in very handy. So eagerly waiting for it to land.

Ocean-OS and jjones315 reacted with thumbs up emoji

@gyzerok
Copy link
Contributor

gyzerok commentedApr 9, 2025
edited
Loading

I've also noticed that the follwing trick helps solving the same problem:

classBox{value;constructor(initial){this.value=initial;}}classCounter{count=$state.raw({inner:newBox(0)});increment(){this.count.inner.value+=1;this.count={inner:this.count.inner}}}

Not as nice as the PRs solution, but solvable without any additions to the API

@Thiagolino8
Copy link

Is it possible to invalidate only one property of an object?

constcounters=$state({count1:0,count2:newCounter()})$effect(()=>console.log(counters.count1))$effect(()=>console.log(counters.count2.current))// Only trigger this onecounters.count2.current++$state.invalidate(counters.count2)

If not, would not it make sense to limit this rune to be used with only $state.raw?

@Ocean-OS
Copy link
ContributorAuthor

I could probably make it do that, but that'd require basically treating it like an effect (and wrapping it in an arrow function), getting the source that corresponds to that property, and invalidating that.

@Thiagolino8
Copy link

Just to clarify, I don't think making it possible to invalidate properties is worth the effort
My point is that if $state.invalidate wasn't designed to use the specific deep fine grain reactivity features of $state then it should be limited to being used with $state.raw

@Ocean-OS
Copy link
ContributorAuthor

Ocean-OS commentedApr 9, 2025
edited
Loading

If I were to try to implement it, it'd be in a separatePR commit so I could easily revert it.
I honestly would be curious to see if I could do it, and if it would work well.

@Ocean-OS
Copy link
ContributorAuthor

Ocean-OS commentedApr 11, 2025
edited
Loading

I've added the ability to invalidate individual properties of a$state proxy (in a pretty performant/simple way too), feel free totry it out and see if there are any bugs.
If there are too many bugs, some sort of confusion, or little reason to have it, I'll revert it.

Thiagolino8 reacted with thumbs up emoji

@Thiagolino8
Copy link

LGTM

@Ocean-OSOcean-OS marked this pull request as ready for reviewApril 19, 2025 23:44
@no-idea-username
Copy link

What about a class version of that object internally created using$state.raw(), with atick() method and a possibility to obtain a raw value without triggering reactivity?

https://svelte.dev/playground/hello-world?version=5.34.7#H4sIAAAAAAAAE41TTY-bQAz9KxZaKaBEoXslBKnKpXtdRb2USkvASUadzKAZQ3aF-O_18BWyyqo9IMBjv_fseW48lV3Qi7wfKKWGqzayAB8LQVgE3so7ConWi341Hn2ULs8FOD5UfS_Lta1RkosdMouP4rlWhIoYxov7YKRLElpZMJVCC2GSqlTFNjeiJJCZOm1Tj2zqcVxcSm0IGnjFI7RwNPoCi3Vo8DgQLDauOJeZtbDTFVMZaFIFkLsf2MK3LgFAqNzghYX4QQS1FoVLSykl1mcJFF53Q8VySWdh1x3Aps-5ZWqJa6lP_tuLo1KZHIiEBaWv8NSMQO1b4GjbVLU3hdzFKBLfeSpFF4qHWDIqZ0GmykkbP-hDALYq0fgMPnbpB8FmIHCvE1KvxLWnqsthnAOAQaqMgq6pOpMVTq1N1Q-H42pnRbOcYaTDMYn8jz-J6bvtZpoPrW7ddGe9d8lx2F94f_nn52Q4jEP-5sihItIKNE-O8bcNj2KbjJBzMW3imHfjLTRjSvduHVEPNRG9ZtcRZyQrk5cR0K6A86HQaNWCgIw4nbgHg1lOohb0EYflv_WZ7Po_Gl3aY50Hk3TP2TiyXBeY_ERjeW2iW3ndR9o47BJ42Yht5UXsHmxXXyztbXfWvJR3e_v5aLa6-N4t4mTjeD-4lVuIYN_d_iCH7_vJUsYgfOY_j26Z27rmkuDOY5zKdfVnS3fWc6bc3zuyZ5p8OLc4I81Renc-cnWPsVxOzr2f32_-y4S8ClV40TGTFtu_-hFo1y8FAAA=

(The code above doesn't work past 5.34.3 without the<svelte:options runes /> declaration at the top)

@no-idea-username
Copy link

[Svelte Playground] - a better example on how to use it

Not a complete solution, but you get the idea.

AState class, that will be specifically designed to work with other classes (or outside.svelte components in general), so that I don't need to deal with limitation of runes and wait for$state.whatever, or even name all my files likefile.svelte.ts just because it's required for runes to work.

I can make a separate feature request later with a more complete idea. For now that PR is the closest thing to what I need.

felbsn reacted with thumbs up emoji

@dummdidumm
Copy link
Member

#16419 brought up an interesting wrinkle, which is not handled by this: If you have an each loop iterating over the values, you want the#each items to re-check their values, too.example

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers
No reviews
Assignees
No one assigned
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

add a way to force update a state variable
6 participants
@Ocean-OS@Thiagolino8@gyzerok@dummdidumm@no-idea-username@Rich-Harris

[8]ページ先頭

©2009-2025 Movatter.jp