-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add $state.invalidate
rune
#15673
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: d4394c5 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
|
I prefer this version as it resembles existing APIs like https://vuejs.org/api/reactivity-advanced.html#triggerref |
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 |
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 let prev;
let value = $derived.by(() => {
prev = value;
// ...
});
// ...
value = prev; |
@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 is described 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 class Model extends AbstractModel {
// 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.
private serverCounter = $state(0)
// this value we will use for UI and will optimistically update
counter = $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 += amount
return async () => {
this.serverCount = await fetch()
}
})
}
}
// somewhere in UI clicks button multiple times and we call increment multiple times
model.increment(1)
model.increment(3)
model.increment(2) The idea here is to have something conceptually similar to what is written in this 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. |
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 |
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 |
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 |
I've also noticed that the follwing trick helps solving the same problem: class Box {
value;
constructor(initial) {
this.value = initial;
}
}
class Counter {
count = $state.raw({ inner: new Box(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 |
Is it possible to invalidate only one property of an object? const counters = $state({
count1: 0,
count2: new Counter()
})
$effect(() => console.log(counters.count1))
$effect(() => console.log(counters.count2.current)) // Only trigger this one
counters.count2.current++
$state.invalidate(counters.count2) If not, would not it make sense to limit this rune to be used with only $state.raw? |
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. |
Just to clarify, I don't think making it possible to invalidate properties is worth the effort |
If I were to try to implement it, it'd be in a separate |
I've added the ability to invalidate individual properties of a |
LGTM |
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 is this), I have noticed some ergonomic issues, such as:$state.opaque
like so:useState
, 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:Closes #14520
Before submitting the PR, please make sure you do the following
feat:
,fix:
,chore:
, ordocs:
.packages/svelte/src
, add a changeset (npx changeset
).Tests and linting
pnpm test
and lint the project withpnpm lint