Basics: Bindings, Lifecycles, Stores
Welcome here,
This article marks the finishing point in covering the most basic Svelte stuff. Recently, we found out about props, template directives, events, and today we are going to review bindings, lifecycles, and stores.
Bindings
What is this
Two-way binding like <my-input [(value)]=”value”></my-input>
in Angular or <Input value={name} onChange={setName} />
in React:
<script>
let name = 'world';
</script>
<input bind:value={name} />
<h1>Hello {name}!</h1>
Automatically converts value type
In DOM everything is a string, but Svelte converts value’s type according to type
attribute:
<input type="number" bind:value={a} min="0" max="10" /> // number
<input type="range" bind:value={a} min="0" max="10" /> //number
<input type="checkbox" bind:checked={yes} /> // boolean
<select bind:value={selected} on:change={() => answer = ''} /> // object
<textarea bind:value></textarea> // string, shorthand form
<textarea bind:value={value}></textarea> // string, full form
Select
initial value
Default value for selected
in <select bind:value={selected}... />
is first entry. Still, while bindings are initialised, selected
remains undefined
, be careful, use Elvis ?
: selected?.id
.
Enabling <select />
multi select ability
<select multiple bind:value={flavours}>
{#each ['vanilla', 'coffee'] as flavour}
<option>{flavour}</option>
{/each}
</select>
Group inputs (radio groups, checkbox groups)
{#each ['vanilla', 'coffee'] as flavour}
<label>
<input
type="checkbox"
name="flavours"
value={flavour}
bind:group={flavours} // it is an array
/>
{flavour}
</label>
{/each}
Lifecycle
-
onMount
does not run inside a server-side component; -
onMount
must be called during the component's initialisation, but doesn't need to live inside the component.
<script>
import { onMount, onDestroy, beforeUpdate, afterUpdate, tick } from 'svelte';
import { paint } from './gradient.js';
...
// After the component is first rendered to the DOM
onMount(() => {
doThings();
return () => cleanup();
});
// Immediately before the DOM is updated. Before mount, too.
beforeUpdate(() => {
if (thing) {
process(thing);
}
});
// Once the DOM is in sync with your data
afterUpdate(() => {
if (thing) {
process(thing);
}
});
// tick() usage example: Await in-progress DOM changes
async function handleSelection() {
someStateUpdates();
await tick();
performSomeOperationThatRequiresUpdatedDOM();
}
// On destroy
onDestroy(anotherCleanup);
});
</script>
Also, lifecycle functions can be called not only in the root ... but also from some kind of callback. Also, they can be called multiple times. For example, the next code sample is absolutely valid:
<script>
let items = new Set();
onMount(() => {
// ...
});
function addItem(fn) {
onMount(() => {
items.add(fn);
return () => items.delete(fn);
});
afterUpdate(async () => {
// ...
await tick();
// ...
});
}
//...
</script>
Component state update ≠ update the DOM immediately
Instead, it waits until the next microtask to see if there are any other changes that need to be applied, including in other components (batching and optimization).
To await updated DOM in the same function execution, we can use await tick(), which resolves when pending DOM updates are completed.
Stores
Dealing with data management, avoid prop drilling.
// stores.js ------------------------------------------------------------
import { writable } from 'svelte/store';
export const count = writable(0);
// Demo.svelte ------------------------------------------------------------
<script>
import { count } from './stores.js';
let count_value;
const unsubscribe = count.subscribe((value) => { // manual subscription (better to use auto-sub $count)
count_value = value;
});
onDestroy(unsubscribe); // requires unsubscription on destroy
const reset = () => count.set(0);
const increment = () => count.update((n) => n + 1);
$: console.log("count is " + $count); // Auto-sub in script
</script>
...
<h1>The count is {count_value}</h1>
<h1>The count is {$count}</h1> // Auto-sub in markup
$value
Auto-subscription
- Works only with store variables that are declared (or imported) at the top-level scope of a component;
-
$
is reserved prefix and it always assumed to refer to a store value.
Writable, Readable, Derived stores
All stores are consumed in the same manner (with different capabilities).
-
Writable as in the example — you can both read and write inside consumer code.
const count = writable(initialValue?)
; -
Readable — readonly in consumers.
const value = readable(initialValue?, startFn, endFn)
where:-
initialValue
- initial value, can benull
orundefined
; - startFn - is called when first subscribe happens (Like initialisation of Hot Observable in RxJS);
- endFn - is called on last unsubscribe.
-
-
Derived — on one hand, like selectors in Redux. On the other hand, can
set
value to itself instead of returning it (useful for async derive).const doubled = derived(stores,deriveFn, initialValue?)
Derived store specifics
Set value
Derived stores can set value? This part was not fully clear at first, but after some exploration I’ve figured out that it is about that you can just derive in async way: instead of instant convert you may hit an API or perform any other async. operation. In that case you can’t return promise or callback to tell that it is async. But what you can do is a call set
or update
in your’s async operation callback to emit the desired output.
import { derived } from 'svelte/store';
const delayed = derived(
a,
($a, set) => {
setTimeout(() => set($a), 1000);
},
2000
);
const delayedIncrement = derived(a, ($a, set, update) => {
set($a);
setTimeout(() => update((x) => x + 1), 1000);
// every time $a produces a value, this produces two
// values, $a immediately and then $a + 1 a second later
});
Can return function from deriveFn
It will be called when a) the callback runs again, or b) the last subscriber unsubscribes.
import { derived } from 'svelte/store';
const tick = derived(
frequency,
($frequency, set) => {
const interval = setInterval(() => {
set(Date.now());
}, 1000 / $frequency);
return () => {
clearInterval(interval);
};
},
2000
);
Custom stores
Custom store is a conventional name in Svelte for such kind of facade that is the code snippet below. Nothing special.
function createCount() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update((n) => n + 1),
decrement: () => update((n) => n - 1),
reset: () => set(0)
};
}
Store bindings
Actual for writable
stores. Usage is the same as with local variables, just prefix with $
:
export const name = writable('world');
...
<input bind:value={$name}>
<button on:click={() => $name += '!'}> // same as: name.set($name + '!')
Add exclamation mark!
</button>
That's all for today, take care, go Svelte!
Top comments (0)