Reputation: 486
I am migrating my codebase to svelte. My business logic contains a lot of classes, which depend on each other. I created a REPL to visualize my problem: Link.
In this example, the money attribute of a person is only updated correctly in the UI if it is directly changed as a html-inline function (left button). When calling a class method (right button), the state of the class is updated, the UI is not.
I am aware this behaviour is intended, and writables should be used to achieve reactivity outside svelte components. Is it somehow possible to achieve reactivity by using classes like in the REPL? Do I need to replace their attributes with writables in order to fix my REPL? If so, how?
<div id="app">
{#each teams as team}
<div>
{#each team.people as person}
<li class="person-row">
<div>{person.name}</div>
<div>{person.money}€</div>
<button on:click={() => (person.money += 1)}>Add 1€ </button>
<button on:click={() => person.addMoney(1)}>Add 1€ </button>
</li>
{/each}
</div>
{/each}
</div>
<script>
class Person {
constructor(name, money) {
this.name = name;
this.money = money;
}
addMoney(x) {
this.money += x;
}
}
class Team {
constructor(name, people) {
this.name = name;
this.people = people;
}
}
const p1 = new Person("Person 1", 1000);
const p2 = new Person("Person 2", 2);
const p3 = new Person("Person 3", 60);
const p4 = new Person("Person 4", 15);
const t1 = new Team("Team 1", [p1, p2]);
const t2 = new Team("Team 2", [p3, p4]);
const teams = [t1, t2];
</script>
<style>
.person-row {
display: flex;
flex-direction: row;
}
.person-row * {
width: 100px;
}
</style>
Upvotes: 4
Views: 2440
Reputation: 29917
After calling a method that mutates an object (or array) add an assignment statement:
person.addMoney(1); person = person
This is the recommended method and allows Svelte to only react to changes inside that person object. (Rewriting the app using raw data and direct mutation might be a better fit for Svelte than migrating classes)
Another other option is to use stores.
Currently using the $store notation only works at the top level.
(Which is helpful for automatic subscribing and unsubscribing)
Your setup with teams and persons makes it hard to create clean setup with the built-in stores.
But the store contract is flexible and powerful and allows using other reactivity systems, like RxJS, Redux and Vue:
import { readable} from "svelte/store"
import { reactive as vueReactive, watch } from 'vue'
function reactive(obj, options) {
const ref = vueReactive(obj)
return readable(ref, (set) => {
return watch(ref, (prev, next) => {
set(ref)
}, options);
})
}
const teams = reactive([t1, t2]);
{#each $teams as team}
Like shown in this REPL
Using this approach has a performance penalty as the teams
store is updated on every addMoney causing a lot more invalidation checks by Svelte.
It's a helpful intermediate step during the migration.
Upvotes: 0
Reputation: 123418
A possible workaround could be to reassign the property to itself, like so:
<button on:click={() => { person.addMoney(1); person.money = person.money; }}
or if you prefer to avoid an explicit reassignment you could also return the money
property from the addMoney()
method
addMoney(x) {
this.money += x;
return this.money;
}
so the button could be
<button on:click={() => { person.money = person.addMoney(1); }}
Upvotes: 1