Deep reactivity in Svelte

#svelte

Deep reactivity in Svelte

What is "deep reactivity"? you ask

From the Vue.js documentation:

In Vue, state is deeply reactive by default. This means you can expect changes to be detected even when you mutate nested objects or arrays

In Vue.js, when using the data option or the reactive() function, a JavaScript object is transformed into an object where each individual property (including those on nested objects) is reactive. Each property in effect becomes its own "store".

In Svelte, there is no way to make object properties reactive like that. Reactivity is only available for local variables declared at the root level of each component.
A reactive "store" from outside the component, must first be assigned to a local variable, and then the store value can be accessed/assigned using a "$" prefix on the local variable.

Most of the time, the Svelte's reactivity model is entirely sufficient and very easy to use.
However, if you need to synchronize a large/complex JavaScript object between multiple components, views, etc. the Vue model is much more convenient.

To "fix" this, I came up with a tiny helper library "ReactivePojo", which brings "deeper" reactivity to Svelte - similar to Vue.

ReactivePojo lets you map a local variable in a Svelte component, to a property on any POJO (Plain Old JavaScript object) - via a custom store (honoring the Svelte store contract) - like this:

let v = RPStore(object, propertyName);

The property value can then be accessed/assigned using the Svelte "$" prefix syntax:

console.log($v); $v = "New value";

Calling RPStore will create a store for the specified object/property - unless one already exists, in which case the existing store is returned. In other words - any call to RPStore for the same object and property name, from anywhere, will always return the same store.
This ensures that two separate Svelte components accessing the same object/property will get the same store and thus the property value will automatically be synchronized between the components (and the underlying object).

The first time RPStore is called for an object/property, the property will be instrumented with getter/setter methods, so that any subsequent assignments directly to the property will also trigger reactivity - ie. subscribers to the store will be notified - and any UI using the store will be updated:

let v = RPStore(Person, "Name"); 
$v = "Bob"; // triggers reactive updates where $v is used 
Person.Name = "Joe"; // also triggers reactive updates where $v is used

This is very similar to the way Vue 2 does reactivity (Vue 3 uses a different technique).

To use this library in a Svelte component:

<script>
import RPStore from "./ReactivePojo.js";
import {Person} from "./MyGlobalData.js"; 
// Note: "Person" object could also come from a property, GetContext(...),  etc.
let Name = RPStore(Person, "Name");
</script>

Name: <input type="text" bind:value={$Name} />

This solution gives you reactivity at a more granular level (like with Vue) - preventing re-calculations/re-rendering based on the entire object when the value of some leaf node property changes.

And just like Vue, it kind of magically makes a POJO reactive.

It is actually more efficient than Vue, because it only adds reactivity to specific properties, rather than traversing and instrumenting every single property in the entire object tree.

"ReactivePojo" is available at https://github.com/jesperhoy/Svelte-ReactivePojo

Jesper Høy's
Dev Blog

  • Home (blog posts)
  • About me and this website
  • My developer tech stack
  • My favorite software
  • My favorite online services
  • Cool stuff
  • My side projects
  • Referral codes
  • Our wonderful ice horses