Skip to main content

Svelte's reactivity is triggered by assignments. Methods that mutate arrays or objects will not trigger updates by themselves.

In this example, clicking the "Add a number" button calls the addNumber function, which appends a number to the array but doesn't trigger the recalculation of sum.

One way to fix that is to assign numbers to itself to tell the compiler it has changed:

ts
function addNumber() {
numbers.push(numbers.length + 1);
numbers = numbers;
}

You could also write this more concisely using the ES6 spread syntax:

ts
function addNumber() {
numbers = [...numbers, numbers.length + 1];
}

The same rule applies to array methods such as pop, shift, and splice and to object methods such as Map.set, Set.add, etc.

Assignments to properties of arrays and objects — e.g. obj.foo += 1 or array[i] = x — work the same way as assignments to the values themselves.

ts
function addNumber() {
numbers[numbers.length] = numbers.length + 1;
}

However, indirect assignments to references such as this...

ts
const foo = obj.foo;
foo.bar = 'baz';

or

ts
function quox(thing) {
thing.foo.bar = 'baz';
}
quox(obj);

...won't trigger reactivity on obj.foo.bar, unless you follow it up with obj = obj.

A simple rule of thumb: the updated variable must directly appear on the left hand side of the assignment.

App.svelte
<script>
let numbers = [1, 2, 3, 4];

function addNumber() {
numbers.push(numbers.length + 1);
}

$: sum = numbers.reduce((t, n) => t + n, 0);
</script>

<p>{numbers.join(' + ')} = {sum}</p>

<button on:click={addNumber}> Add a number </button>

/* App.svelte generated by Svelte v4.2.19 */
import {
SvelteComponent,
append,
detach,
element,
init,
insert,
listen,
noop,
safe_not_equal,
set_data,
space,
text
} from "svelte/internal";

import "svelte/internal/disclose-version";

function create_fragment(ctx) {
let p;
let t0_value = /*numbers*/ ctx[1].join(' + ') + "";
let t0;
let t1;
let t2;
let t3;
let button;
let mounted;
let dispose;

return {
c() {
p = element("p");
t0 = text(t0_value);
t1 = text(" = ");
t2 = text(/*sum*/ ctx[0]);
t3 = space();
button = element("button");
button.textContent = "Add a number";
},
m(target, anchor) {
insert(target, p, anchor);
append(p, t0);
append(p, t1);
append(p, t2);
insert(target, t3, anchor);
insert(target, button, anchor);

if (!mounted) {
dispose = listen(button, "click", /*addNumber*/ ctx[2]);
mounted = true;
}
},
p(ctx, [dirty]) {
if (dirty & /*sum*/ 1) set_data(t2, /*sum*/ ctx[0]);
},
i: noop,
o: noop,
d(detaching) {
if (detaching) {
detach(p);
detach(t3);
detach(button);
}

mounted = false;
dispose();
}
};
}

function instance($$self, $$props, $$invalidate) {
let sum;
let numbers = [1, 2, 3, 4];

function addNumber() {
numbers.push(numbers.length + 1);
}

$: $$invalidate(0, sum = numbers.reduce((t, n) => t + n, 0));
return [sum, numbers, addNumber];
}

class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}

export default App;
result = svelte.compile(source, {
generate:
css:
});
/* Add a <style> tag to see compiled CSS */