> [!NOTE] When you update properties of proxies, the original object is _not_ mutated.
> [!NOTE] When you update properties of proxies, the original object is _not_ mutated.
Since `$state` stops at boundaries that are not simple arrays or objects, the following will not trigger any reactivity:
```svelte
<script>
class Todo {
done = false;
text;
constructor(text) {
this.text = text;
}
}
let todo = $state(new Todo('Buy groceries'));
</script>
<buttononclick={
// this won't trigger a rerender
todo.done = !todo.done
}>
[{todo.done ? 'x' : ' '}] {todo.text}
</button>
```
You can however use `$state`_inside_ the class to make it work, as explained in the next section.
### Classes
### Classes
You can also use `$state` in class fields (whether public or private):
You can use `$state` in class fields (whether public or private):
```js
```js
// @errors: 7006 2554
// @errors: 7006 2554
@ -85,7 +111,7 @@ class Todo {
}
}
```
```
> [!NOTE] The compiler transforms `done` and `text` into `get`/`set` methods on the class prototype referencing private fields.
Under the hood, the compiler transforms `done` and `text` into `get`/`set` methods on the class prototype referencing private fields. That means the properties are _not_ enumerable.
## `$state.raw`
## `$state.raw`
@ -111,6 +137,136 @@ person = {
This can improve performance with large arrays and objects that you weren't planning to mutate anyway, since it avoids the cost of making them reactive. Note that raw state can _contain_ reactive state (for example, a raw array of reactive objects).
This can improve performance with large arrays and objects that you weren't planning to mutate anyway, since it avoids the cost of making them reactive. Note that raw state can _contain_ reactive state (for example, a raw array of reactive objects).
## Passing `$state` across boundaries
Since there's no wrapper around `$state` or `$state.raw` (or [`$derived`]($derived)), you have to be aware of keeping reactivity alive when passing it across boundaries, e.g. when you pass a reactive object into or out of a function. The most succinct way of thinking about this is to treat `$state` and `$state.raw` (and [`$derived`]($derived)) as "just JavaScript", and reuse the knowledge of how normal JavaScript variables work when crossing boundaries. Take the following example:
```js
function createTodo(initial) {
let text = initial;
let done = false;
return {
text,
done,
log: () => console.log(text, done)
}
}
const todo = createTodo('wrong');
todo.log(); // logs "'wrong', false"
todo.done = true;
todo.log(); // still logs "'wrong', false"
```
The value change does not propagate back into the function body of `createTodo`, because `text` and `done` are read at the point of return, and are therefore a fixed value. To make that work, we have to bring the read and write into the scope of the function body. This can be done via getter/setters or via function calls:
```js
function createTodo(initial) {
let text = initial;
let done = false;
return {
// using getter/setter
get text() { return text },
set text(v) { text = v },
// using functions
isDone() { return done },
toggle() { done = !done },
// log
log: () => console.log(text, done)
}
}
const todo = createTodo('right');
todo.log(); // logs "'right', false"
todo.text = 'changed'; // invokes the setter
todo.toggle(); // invokes the function
todo.log(); // logs "'changed', true"
```
What you could also do is to instead create an object and return that as a whole. While the variable itself is fixed in time, its properties are not, and so they can be changed from the outside and the changes are observable from within the function:
```js
function createTodo(initial) {
const todo = { text: initial, done: false }
return {
todo,
log: () => console.log(text, done)
}
}
const todo = createTodo('right');
todo.log(); // logs "'right', false"
todo.todo.done = true; // mutates the object
todo.log(); // logs "'right', true"
```
Classes are similar, their properties are "live" due to the `this` context:
```js
class Todo {
done = false;
text;
constructor(text) {
this.text = text;
}
log() {
console.log(this.done, this.text)
}
}
const todo = new Todo('right');
todo.log(); // logs "'right', false"
todo.done = true;
todo.log(); // logs "'right', true"
```
Notice how we didn't use _any_ Svelte specifics, this is just regular JavaScript semantics. `$state` and `$state.raw` (and [`$derived`]($derived)) don't change these, they just add reactivity on top, so that when you change a variable something can happen in reaction to it.
As a consequence, the answer to preserving reactivity across boundaries is to use getters/setters or functions (in case of `$state`, `$state.raw` and `$derived`) or an object with mutable properties (in case of `$state`), or a class with reactive properties.
@ -51,3 +51,7 @@ In essence, `$derived(expression)` is equivalent to `$derived.by(() => expressio
Anything read synchronously inside the `$derived` expression (or `$derived.by` function body) is considered a _dependency_ of the derived state. When the state changes, the derived will be marked as _dirty_ and recalculated when it is next read.
Anything read synchronously inside the `$derived` expression (or `$derived.by` function body) is considered a _dependency_ of the derived state. When the state changes, the derived will be marked as _dirty_ and recalculated when it is next read.
To exempt a piece of state from being treated as a dependency, use [`untrack`](svelte#untrack).
To exempt a piece of state from being treated as a dependency, use [`untrack`](svelte#untrack).
## Passing `$derived` across boundaries
The same rules as for [passing `$state` across boundaries]($state#Passing-$state-across-boundaries) apply.
@ -63,7 +63,7 @@ The context is then available to children of the component (including slotted co
> [!NOTE] `setContext`/`getContext` must be called during component initialisation.
> [!NOTE] `setContext`/`getContext` must be called during component initialisation.
Context is not inherently reactive. If you need reactive values in context then you can pass a `$state` object into context, whose properties _will_ be reactive.
Context is not inherently reactive. If you need reactive values in context then you can pass a `$state` object into context, whose properties _will_ be reactive (for more info see ["passing `$state` across boundaries"]($state#Passing-$state-across-boundaries)).
```svelte
```svelte
<!--- file: Parent.svelte --->
<!--- file: Parent.svelte --->
@ -72,6 +72,8 @@ Context is not inherently reactive. If you need reactive values in context then
let value = $state({ count: 0 });
let value = $state({ count: 0 });
setContext('counter', value);
setContext('counter', value);
// careful: reassignments will _not_ change the value inside context: