Svelte journey | Bindings, Lifecycles, Stores
Tags: svelte, javascript, tutorial, frontend
Published: 2023-12-23 10:39:02

# 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:

```jsx
<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:

```jsx
<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

```jsx
<select multiple bind:value={flavours}>
  {#each ['vanilla', 'coffee'] as flavour}
		<option>{flavour}</option>
	{/each}
</select>
```

### Group inputs (radio groups, checkbox groups)

```jsx
{#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.

```jsx
<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 <script>...</script> but also from some kind of callback. Also, they can be called multiple times. For example, the next code sample is absolutely valid:

```jsx
<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.

```jsx
// 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 be `null` or `undefined`;
    - startFn - is called when first subscribe happens (Like initialisation of [Hot Observable in RxJS](https://luukgruijs.medium.com/understanding-hot-vs-cold-observables-62d04cf92e03));
    - endFn - is called on last unsubscribe.
- **[Derived](https://svelte.dev/docs/svelte-store#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**.

```jsx
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.

```jsx
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.

```jsx
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 `$`:

```jsx
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!

## Resources
[Svelte Tutorial](https://learn.svelte.dev/tutorial)
[Svelte Documentation | Stores](https://svelte.dev/docs/svelte-store)