2020-06-20

Svelte tutorial note

svelte, javascript, selfnote

banner

Image by William Krause from Unsplash

This is a note as I wrote down as I was going through Svelte tutorial.

Might be of helpful for some but foremost, this is a note for myself :)

1. Introduction

Creating a new Svelte project

https://svelte.dev/blog/svelte-for-new-developers

npx degit sveltejs/template new-project-name

VS Code

Install following extensions

  1. Svelte
  2. Svelte Intellisense

2. Reactivity

a. Assignments

https://svelte.dev/tutorial/reactive-assignments

on:click on looks like a directive and click is the event name.
States are reactive, closure under script tag re-renders whenever the state value changes.

1<script>2  let count = 0;3
4  function handleClick() {5    count++;6  }7</script>8
9<button on:click="{handleClick}">Clicked {count} {count === 1 ? 'time' : 'times'}</button>

b. Declarations

https://svelte.dev/tutorial/reactive-declarations

Computed/derived states need to be declared using a special syntax, $:.

1let count = 0;2$: doubled = count * 2;

Useful when it needs to be access multiple times.
Instead of {count * 2} everywhere, you can use {doubled} instead.

c. Statements

https://svelte.dev/tutorial/reactive-statements

$: isn't limited to expressions (reactive values) but also to statements.

1<script>2  let count = 0;3
4  $: console.log(`the count is ${count}`);5
6  $: if (count >= 10) {7    alert(`count is too high!`);8    count = 9;9  }10
11  function handleClick() {12    count += 1;13  }14</script>15
16<button on:click="{handleClick}">Clicked {count} {count === 1 ? 'time' : 'times'}</button>

d. Updating arrays and objects

https://svelte.dev/tutorial/updating-arrays-and-objects

A simple rule of thumb: the name of the updated variable must appear on the left hand side of the assignment.
Or assign a new reference like you do in React.

1// Instead of this2function addNumber() {3  numbers.push(numbers.length + 1);4  numbers = numbers;5}6
7// Do this8function addNumber() {9  numbers = [...numbers, numbers.length + 1];10}

3. Props

a. Declaring props

https://svelte.dev/tutorial/declaring-props

For passing data to another component(s). Same concept as it does in React.

In React, components receive props but in Svelte, you export a variable.

Nested.svelte

1<script>2  export let answer;3</script>4<p>The answer is {answer}</p>

App.svelte imports Nested component and passes the answer like following.

1<script>2  import Nested from "./Nested.svelte";3</script>4
5<Nested answer="{42}"></Nested>

b. Default values

https://svelte.dev/tutorial/default-values

You can set the default prop value during declaration

Nested.svelte

1<script>2  export let answer = "is unknown!";3</script>4<p>The answer is {answer}</p>

If no props passed to Nested like <Nested>, then the default value is used.

c. Spread props

https://svelte.dev/tutorial/spread-props

As you can do in React, you can pass multiple props with object spread operator.

<Info {...pkg}></Info>

4. Logic

a. If blocks

https://svelte.dev/tutorial/if-blocks

The markup is avaiable in Svelte only, not in HTML.

1{#if user.loggedIn}2<button on:click="{toggle}">Log Out</button>3{/if} {#if !user.loggedIn}4<button on:click="{toggle}">Log In</button>5{/if}

b. Else blocks

https://svelte.dev/tutorial/else-blocks

Mutually exclusive condition can use {:else} block

1{#if user.loggedIn}2<button on:click="{toggle}">Log Out</button>3{:else}4<button on:click="{toggle}">Log In</button>5{/if}

c. Else-if blocks

https://svelte.dev/tutorial/else-if-blocks

Additional condition can be checked with {:else if condition}

1{#if x > 10}2<p>{x} is greater than 10!</p>3{:else if x < 5 }4<p>{x} is less than 5 {:else}</p>5
6<p>{x} is 'teween 5 and 10</p>7{/if}

d. Each blocks

https://svelte.dev/tutorial/each-blocks

You can iterate an iterable object with {#each iterable as alias, index}

1<ul>2  {#each cats as cat, index}3  <li>{index + 1}th cat is {cat.name}</li>4  {/each}5</ul>

The alias can be destructured like

1{#each cats as {name, id, age}, index}2<li>{index + 1}th cat is {name} and is {age} years old</li>3{/each}

e. Keyed each blocks

https://svelte.dev/tutorial/keyed-each-blocks

In React, creating an iterable element requires key per each element.

e.g.)

1{2  things.map(thing => <li key={thing.id}>{thing.color}</li>);3}

In Svelte, you specify the key in the markup.

1{#each things as thing (thing.id)}2<li>{thing.color}</li>3{/each}

Or you can destructure thing

1{#each things as {id, color} (id)}2<Thing current="{color}" />3{/each}

f. Await blocks

https://svelte.dev/tutorial/await-blocks

Svelte markup has a way to await promises.
Race condition is handled automatically because Svelte only grabs the latest/most recent promise only.

1{#await promise}2<p>Loading...</p>3{:then number}4<p>The value is {number}</p>5<p>{:catch error}</p>6
7<p class="error">{error.message}</p>8{/await}

You can decide not to show the intermediate "loading" message and wait 'til the promise resolves.

1{#await promise then number}2<p>The value is {number}</p>3<p>{/await}</p>

This is much cleaner abstraction than in React, in which one needs to use useEffect to resolve promise in an async method and set the state.

5. Events

a. DOM events

https://svelte.dev/tutorial/dom-events

Use on: directive, followed by DOM event name.

e.g.) mousemove

1<script>2  let m = { x: 0, y: 0 };3
4  function handleMousemove(event) {5    m.x = event.clientX;6    m.y = event.clientY;7  }8</script>9
10<style>11  div {12    width: 100%;13    height: 100%;14  }15</style>16
17<div on:mousemove="{handleMousemove}">The mouse position is {m.x} x {m.y}</div>

b. Inline handlers

https://svelte.dev/tutorial/inline-handlers

โš  Inline event handlers does not cause any performance issues unlike in React, as Svelte knows how to optimize.

Instead of,

<div on:mousemove="{handleMousemove}">The mouse position is {m.x} x {m.y}</div>

You can inline handleMousemove as shown below.

1<div on:mousemove={e => m = {x: e.clientX, y: e.clientY}}>2	The mouse position is {m.x} x {m.y}3</div>

Or, wrap the inline method in quotes for syntax highlighting in some editors

1<div on:mousemove="{e => m = {x: e.clientX, y: e.clientY}}">2  The mouse position is {m.x} x {m.y}3</div>

c. Event modifiers

https://svelte.dev/tutorial/event-modifiers

You can "decorate" (my intepretaion) event with modifiers such as

  • once: run the handler once
  • prevetnDefault: event.preventDefault() before calling the handler
  • stopPropagation: event.stopPropagation() to stop the event bubble/capture
  • passive: for touch/wheel scrolling performance (Google added it as a non-standard but it's supported widely)
  • capture: DOM events "bubble-up" by default. This reverses it as capture (Refer to MDN Event.eventPhase)
  • self: event.target === current element.

e.g.) using once to run an event handler only once on a button

<button on:click|once="{handleClick}">Click me</button>

Modifiers are chainable. on:click|once|capture|preventDefault

handleClick will be called once once no matter how many times you press the button.

โš  Space is significant! The code below is not valid as there are spaces between |.

<button on:click | once="{handleClick}">Click me</button>

d. Component events

https://svelte.dev/tutorial/component-events

Unlike custom event dispatch in vanilla JS, where you pass custom data as detail property,

1// add an appropriate event listener2obj.addEventListener("cat", function(e) { process(e.detail) });3
4// create and dispatch the event5let event = new CustomEvent("cat", {6  ๐Ÿ‘‡7  detail: {8    hazcheeseburger: true9  }10});11obj.dispatchEvent(event);

you dispatch an event with data and it will be available as part of event.detail automatically.

Inner.svelte

1<script>2  import { createEventDispatcher } from "svelte";3
4  const dispatch = createEventDispatcher();5
6  function sayHello() {7    // NOT THIS!8    // dispatch('message', {detail: {text: 'hi!'}})9    // But pass the data as it is10    dispatch("message", { text: "Hello!" });11  }12</script>13
14<button on:click="{sayHello}">Click to say hello</button>

You can then use the component and subscribe to the event, message like following.

App.svelte

1<script>2  import Inner from "./Inner.svelte";3
4  function handleMessage(event) {5    // Access "text" via ๐Ÿ‘‡ event.detail6    alert(event.detail.text);7  }8</script>9
10<Inner on:message="{handleMessage}" />

This pattern is different from React where an inner component receives an "event handler" as a function and calls it, not declare an event.

1const App = () => <Inner onMessage={handleMessage}>2const Inner = ({onMessage}) => <button onClick={onMessage}>Click</button>

So it seems that in Svelte, event handlers are declared using vanilla JavaScript's CustomEvent interface.

e. Event forwarding

https://svelte.dev/tutorial/event-forwarding

DOM events are bubbled up while Svelte events aren't. Explicit event forwarding can be done by creating event dispatcher in each level.

Svelte can forward events with a shortcut where you specify the on:eventname directive without a value.

<Inner on:message></Inner>

Then all on:message event handlers will be forwarded up and made available to the calling component.

Note: this is tough to grasp. Need to come back later.

f. DOM event forwarding

https://svelte.dev/tutorial/dom-event-forwarding

Svelte requires you explicitly decide whether to expose an event or not.

When there is more than one element in inner component exposing the same event, say two buttons with on:click,

CustomButton.svelte

1<button id="b1" on:click>Click me</button>2
3<button id="b2" on:click>Click me2</button>

Then you can tell which one was fired by examining event.target

App.svelte

1<script>2  import CustomButton from "./CustomButton.svelte";3
4  function handleClick(event) {5    console.log(`e =>`, event.target);6  }7</script>8
9<CustomButton on:click="{handleClick}"> /></CustomButton>

CustomButton click on #b1 and #b2 results in,

1e => <button id=โ€‹"b1">โ€‹Click meโ€‹</button>โ€‹2e => <button id=โ€‹"b2">โ€‹Click me2โ€‹</button>โ€‹

6. Bindings

a. Text inputs

https://svelte.dev/tutorial/text-inputs

Sorta like a two-way binding, where changes in an element updates the state and the current state.

1<script>2  let name = "world!";3</script>4
5<input bind:value="{name}" />6
7<h1>Hello {name}!</h1>

Updating values in input will update name state as well as the input's value.

b. Numeric inputs

https://svelte.dev/tutorial/numeric-inputs

batteries included

Svelte auto converts input of type number & range to numeric values.
React requires explicit conversion as it's metal.

c. Checkbox inputs

https://svelte.dev/tutorial/checkbox-inputs

Checkbox input type value is bound to bind:checked instead of bind:value.

1<script>2  let isChecked = false;3</script>4<input type="checkbox" bind:checked="{isChecked}" />

d. Group inputs

https://svelte.dev/tutorial/group-inputs

In vanilla JS, you use name to group related checkboxes and radio.
MDN Reference: <input type="radio">

1<form>2  ๐Ÿ‘‡3  <input type="radio" name="scoops" value="1" />4  <input type="radio" name="scoops" value="2" />5  <input type="radio" name="scoops" value="3" />6</form>

but in Svelte, you bind a group using bind:group directive.

1<form>2  ๐Ÿ‘‡3  <input type="radio" bind:group="scoops" value="1" />4  <input type="radio" bind:group="scoops" value="2" />5  <input type="radio" bind:group="scoops" value="3" />6</form>

When bound to a radio group, then the bound value is one value, but on checkboxes, the bound value is an array.

1<script>2  let scoops = 1;3  let flavours = [];4</script>5
6<!-- Radio `scopes` bound to a single value -->7<label>8  <input type="radio" bind:group="{scoops}" value="{1}" />9  One scoop10</label>11<label>12  <input type="radio" bind:group="{scoops}" value="{2}" />13  Two scoops14</label>15<label>16  <input type="radio" bind:group="{scoops}" value="{3}" />17  Three scoops18</label>19
20<!-- Checkbox group value, `favlours` is an array -->21<label>22  <input type="checkbox" bind:group="{flavours}" value="Cookies and cream" />23  Cookies and cream24</label>25<label>26  <input type="checkbox" bind:group="{flavours}" value="Mint choc chip" />27  Mint choc chip28</label>29<label>30  <input type="checkbox" bind:group="{flavours}" value="Raspberry ripple" />31  Raspberry ripple32</label>

e. Textarea inputs

https://svelte.dev/tutorial/textarea-inputs

Same as <input type="text">. You bind value using bind:value={value}. If the value variable name matches value, then you can leave out the assignment, like,

<textarea bind:value></textarea>

f. Select bindings

https://svelte.dev/tutorial/select-bindings

Like Textarea, you can use bind:value={value} and leave out the assignment, bind:value if the variable name is value.

1<script>2let value;3let answer = ""4const questions = [5	{id: 1, 'question #1'},6	{id: 2, 'question #2'},7	{id: 3, 'question #3'},8]9</script>10
11<!-- this works too	๐Ÿ‘‡ -->12<!-- <select bind:value={value} on:change="{() => answer = ""}"> -->13<select bind:value on:change="{() => answer = ""}">14	{#each questions as question}15		<option value={question}>{question.text}</option>16	{/each}17</select>18
19<input bind:value={answer}>

g. Select multiple

https://svelte.dev/tutorial/multiple-select-bindings

I've already mentioned this in d. Group inputs - https://svelte.dev/tutorial/group-inputs

Binding to a select with multiple directive sets the value to an array.

flavours is an array.

1<select multiple bind:value="{flavours}">2  {#each menu as flavour}3  <option value="{flavour}">{flavour}</option>4  {/each}5</select>

h. Contenteditable bindings

https://svelte.dev/tutorial/contenteditable-bindings

You can bind to either textContent or innerHTML

1<div contenteditable="true" bind:innerHTML="{html}"></div>2<!-- or -->3<div contenteditable="true" bind:textContent="{html}"></div>

Check out the difference between textContent & innerHTML
& why one should consider using textContent over innerHTML.

i. Each block bindings

https://svelte.dev/tutorial/each-block-bindings

Don't use this if you plan to go with immutable data (React style).
Familiar with imperative style? go with this.

j. Medial elements

https://svelte.dev/tutorial/media-elements

Media elements' (video/audio) are updated more frequently using requestAnimationFrame.

k. Dimensions

https://svelte.dev/tutorial/dimensions

Every block-level elements, such as div, section, article, etc have bindings to following props.

l. This

https://svelte.dev/tutorial/bind-this

bind:this={variable} returns a reference to rendered elements.
variable will be undefined until the component has mounted.
Use onMount lifecycle to refer to the variable.

Note: This looks like ref in React.

m. Component bindings

https://svelte.dev/tutorial/component-bindings

As mentioned previously, you can bind:value for custom components to provide a two-way binding.

Changes in child component will be available in the parent element.

Keypad.svelte

1<script>2  export let value;3</script>4...

Suppose that in App.svelte,

1<script>2  import Keypad from "./Keypad.svelte";3
4  let pin;5
6  const handleSubmit = () => console.log(`pin => ${pin}`);7</script>8
9<input bind:value="{pin}" />10<Keypad bind:value="{pin}" on:submit="{handleSubmit}"></Keypad>

You can bind to Keypad with bind:value={pin}. It works as both an input and output to Keypad component.
It can be demo'ed by changing values in <input bind:value={pin} />.

Awesome! Very convinient. But you have to be careful as you can lose track of the state flow.

In React, one would have to pass a callback function to call it whenever child value changes and the parent would update the state via the callback.

App.jsx

function App() { const [pin, setPin] = React.useState(null) return <Keypad onChange="{setPin}" /> }

7. Lifecycle

a. onMount

https://svelte.dev/tutorial/onmount

It's comparable to the mix of componentDidMount and useEffect because it's called when a component is mounted, and cleans up with a callback function returned from it (that's how useEffect does a clean up).

And also, componentDidMount can be async and useEffect cannot call an async method.

As it's the recommended way to call fetch in React, onMount is normally where one should make a network request.

1<script>2  import { onMount } from "svelte";3
4  onMount(async () => {5    const response = await fetch("https://www...");6    photos = await response.json();7
8    return () => {9      // clean up resources here10    };11  });12</script>

b. onDestroy

https://svelte.dev/tutorial/ondestroy

onDestroy is like React's componentWillUnmount. Use it clean up resources on component's unmount phase.

1<script>2  import { onDestroy } from "svelte";3
4  let seconds = 1;5  const id = setInterval(() => seconds++, 1000);6
7  onDestroy(() => void clearInterval(id));8</script>

c. beforeUpdate and afterUpdate

https://svelte.dev/tutorial/update

Flows like,

beforeUpdate -> onMount -> beforeUpdate -> state changes -> afterUpdate -> onDestroy

As beforeUpdate runs BEFORE onMount, one needs to check for existence of elements

d. tick

https://svelte.dev/tutorial/tick

To get around batch processing (state updates, DOM updates, etc)

1<script>2  import { tick } from "svelte";3</script>

8. Stores

a. Writable stores

https://svelte.dev/tutorial/writable-stores

Svelte has batteries included. It comes with a global state management library.

svelte/store has writable method to create a global state.

store.js

1import { writable } from "svelte/store";2
3export const count = writable(0);

Then one can import count in store.js, either to read, update, or set the value.

  1. Reading via subscription
    • writable returns a state, which you can subscribe() for the value change
    • It is a HoF (higher-order function), which returns a function to unsubscribe
    • It's the same as how Redux store's subscribe returns unsubscribe method
    • My guess is that you need to call unsubscribe in onDestroy normally to clean up
1<script>2  import { onDestroy } from "svelte";3  import { count } from "./store";4
5  let countValue;6  const unsubscribe = count.subscribe(value => {7    countValue = value;8  });9  // Clean up after your business!10  onDestroy(unsubscribe);11</script>
  1. Updating the state - writable returns a state, which you can update values for
    • It requires a callback, which is given the current value to update with
1<script>2  import { count } from "./store.js";3  const incrementCount = () => count.update(currentValue => currentValue + 1);4</script>5
6<button on:click="{incrementCount}">Increment Count by One/button></button>
  1. Setting the state (convinience method for update)
    • set method looks like a convinience method to update
    • as you can simply set a value without a callback function
1<script>2  import { count } from "./store.js";3  const reset = () => count.set(0);4</script>5
6<button on:click="{reset}">Reset Count</button>

b. Auto-subscriptions

https://svelte.dev/tutorial/auto-subscriptions

Svelte has yet another convinient way to subscribe to the global state change.
With $ prefixed to a variable, Svelte takes care of both un/subscription out of the box.

Instead of this verbose un/subscribe for count,

1<script>2  import { onDestroy } from "svelte";3  import { count } from "./store";4
5  let countValue;6  const unsubscribe = count.subscribe(value => {7    countValue = value;8  });9  // Clean up after your business!10  onDestroy(unsubscribe);11</script>12
13<p>Count value is {countValue}</p>

You can simply prefix count with $ like $count.

1<script>2  import { onDestroy } from "svelte";3  import { count } from "./store";4</script>5
6<p>Count value is {$count}</p>

Make sure to read notes in the linked page.

c. Readable stores

https://svelte.dev/tutorial/readable-stores

Readable store provides, duh, read-only store, for which one can initialize but can't update.
It looks similar to useEffect that the returned function is called when "the last subscriber unsubscribes".

store.js

1import { readable } from "svelte";2
3const initialValue = new Date();4const valueUpdator = set => {5  const id = setInterval(() => set(new Date()), 1000);6
7  // called when the last subscriber unsubscribes.8  return () => clearInterval(id);9};10
11export const time = readable(initialValue, valueUpdator);

And the same as wriable store, you can refer to it with $ prefix, like $time in another file.

d. Derived stores

  • Tutorial: https://svelte.dev/tutorial/derived-stores
  • API: https://svelte.dev/docs#derived

The tutorial prefixes time with $ like $time in the callback.

Auto-subscriptions tutorial states that

Any name beginning with $ is assumed to refer to a store value. It's effectively a reserved character โ€” Svelte will prevent you from declaring your own variables with a $ prefix.

But I tried it without $ prefix as shown below but still works.

export const elapsed = derived(time, t => Math.round((t - start) / 1000));

Not sure if $ is required. Left a question on Reddit.
https://www.reddit.com/r/sveltejs/comments/hblmxa/question_derived_callback_in_tutorial_uses_a/

e. Custom stores

https://svelte.dev/tutorial/custom-stores

One can create a custom store by implementing subscribe method.
Tutorial uses wriable's subscribe to expose the interface and doesn't show how to implement one yourself.

f. Store bindings

https://svelte.dev/tutorial/store-bindings

Store value referred to with $ prefix can be bound as if it's a local state.

1<script>2  import { name } from "./store.js";3</script>4
5<input bind:value="{$name}" />

Typing in the input will update $name and will trigger update itself and all dependents.

9. Motion

a. Tweened

https://svelte.dev/tutorial/tweened

Svelte has a built-in motion library without having to install a 3rd party library.
In React, you'd use react-spring, or react-motion, etc.

b. Spring

https://svelte.dev/tutorial/spring

Use this instead of tweened for frequently changing values

10. Transitions

a. The transition directive

https://svelte.dev/tutorial/transition

Another batteries-included way to provide transition in JavaScript.
According to Chrome Devtools, <p transition:fade> injects an inline style to fade in/out.

1<script>2  import { fade } from "svelte/transition";3  let visible = true;4</script>5
6{#if visible}7<p transition:fade>Fade in and out</p>8{/if}

b. Adding parameters

https://svelte.dev/tutorial/adding-parameters-to-transitions

You can also pass in-line parameters to transition functions in the markup.

1<script>2  import { fly } from "svelte/transition";3  let visible = true;4</script>5
6<input type="checkbox" bind:checked="{visible}" />7
8{#if visible}9<p transition:fly="{{ y: 200, duration: 2000 }}">Flies in and out</p>10{/if}

Transitions are "reversible". Toggling visibility doesn't abruptly starts transition from beinging or the end.
It reverses where it left off.
Refer to the linked tutorial page to see it in action! Cool stuff.

c. In and out

https://svelte.dev/tutorial/in-and-out

You can granularly contorl transition with in & out directives instead of transition.

d. Custom CSS transitions

https://svelte.dev/tutorial/custom-css-transitions

Looks simple so long as you undersand CSS transition and motions etc.
I know neither well so it's tough.

To learn first: Using CSS transitions on MDN.

e. Custom JS transitions

https://svelte.dev/tutorial/custom-js-transitions

Use tick callback to implement JS transitions for effects not possible by CSS transitions.

f. Transition events

https://svelte.dev/tutorial/transition-events

Monitor transition directive events with following directives

  • on:introstart
  • on:outrostart
  • on:introend
  • on:outroend

g. Local transitions

https://svelte.dev/tutorial/local-transitions

local transition makes transitions to occur on individual elements, not for a group of items.

Honestly, I really haven't found a use for this.

h. Deferred transitions

https://svelte.dev/tutorial/deferred-transitions

More advanced transition concept I'd have to learn later.

11. Animations

a. The animate directive

https://svelte.dev/tutorial/animate

Oh boy. come back later...

12. Actions

a. The use directive

https://svelte.dev/tutorial/actions

Use use: directive to specify the action.

1<script>2  import { pannable } from "./pannable.js";3</script>4<div use:pannable></div>

pannable is a function, which accepts a DOM node.

1// Fires following custom events2// 1. panstart3// 2. panmove4// 3. panend5export function pannable(node) {}

When the pannable dispatches a custom event, the parent component can subscribe to it in the markup.

1<script>2  import { pannable } from "./pannable.js";3
4  // These functions have access to `event` dispatched from `pannable`5  const handlePanStart = event => {};6  const handlePanMove = event => {};7  const handlePanEnd = event => {};8</script>9<div10  use:pannable11  on:panstart="{handlePanStart}"12  on:panmove="{handlePanMove}"13  on:panend="{handlePanEnd}"14  style="transform:15		translate({$coords.x}px,{$coords.y}px)16		rotate({$coords.x * 0.2}deg)"17></div>

Clean up of the action can be done by exposing onDestroy.

1export function pannable(node) {2  return {3    onDesotry() {4      // clean up the mess5    },6  };7}

b. Adding parameters

https://svelte.dev/tutorial/adding-parameters-to-actions

Just like transitions, actions can accept arguments.

1<script>2  import { longpress } from "./longpress.js";3</script>4<div use:longpress="{duration}"></div>

When the duration is changed, longpress.js won't know that the duration has changed.
To subscribe to the duration change, implement update function in the action

longpress.js

1export function longpress(node, duration) {2  return {3    update(newDuration) {4      duration = newDuration;5    },6  };7}

Multiple arguments can be passed to the action as an object

1<script>2  import { longpress } from "./longpress.js";3</script>4<div use:longpress="{{duration," spiciness}}></div>

and accept the object in the action.

longpress.js

export function longpress(node, { duration, spiciness }) {}

13. Classes

a. The class directive

https://svelte.dev/tutorial/classes

Svelt provides a shortcut for class toggle.

It's just class in Svelte not className as it is in React.

1<script>2  let current = "foo";3</script>4<style>5  .someActiveClass {6    background-color: red;7    color: white;8  }9</style>10
11<button class:someActiveClass="{current='foo'}" on:click="{() => current = 'foo'}">>foo</button>12
13<button class:someActiveClass="{current='bar'}" on:click="{() => current = 'bar'}">>bar</button>14
15<button class:someActiveClass="{current='baz'}" on:click="{() => current = 'baz'}">>baz</button>

Whenever the condition matches, the custom class append after class: is added.

b. Shorthand class directive

https://svelte.dev/tutorial/class-shorthand

The shorthand for the shortcut (whew, what a mouthful) is that you can leave out the directive assignment if the class to toggle matches the variable name.

<div class:big="{big}"></div>

can be shortened to

<div class:big></div>

14. Component composition

a. Slots

https://svelte.dev/tutorial/slots

This is just like React's children to specify where to put child components in the current one.

Svelte component is not a function, but more like a markup w/ scripts and styles.
So to access children, you need to specify <slot></slot> or <slot />.

You can specify multiple <slot />, which will show the children multiple times.

box.svelte

1<style>2  .box {3  }4</style>5
6<div class="box">7  <slot></slot>8  <!-- or -->9  <slot />10</div>

And pass the children to the box component.

1<script>2  import Box from "./box.svelte";3</script>4
5<Box>6  <h1>Here is the child header</h1>7  <p>this is the content</p>8  <p></p9></Box>

Personal note: This is more to how React should have been as React's supposed to be declarative.
Svelte correctly uses the markup declration for the child, while React is imperative with children. (Not to mention children can be anything like a function to implement render props).

b. Slot fallbacks

https://svelte.dev/tutorial/slot-fallbacks

If you weren't specifying any fallback, you could use <slot /> but to provide a fallback (when a user didn't specify a child), then you can use a longer <slot>fallback content</slot>.

box.svelte

1<style>2  .box {3  }4</style>5
6<div class="box">7  <slot>Fallback content!!!</slot>8</div>

The example of none-child passed to Box is as shown below

1<script>2  import Box from "./Box.svelte";3</script>4
5<Box>6  <h2>Hello!</h2>7  <p>This is a box. It can contain anything.</p>8</Box>9
10<Box></Box>11<Box />

c. Named slot

https://svelte.dev/tutorial/named-slots

In React, one would expose seprate components or static child components like this.

1function App() {2  return (3    <ContactCard>4      <ContactCard.Name>Sung Kim</ContactCard.Name>5      <ContactCard.Address />6    </ContactCard>7  );8}9// or10function App() {11  return (12    <ContactCard>13      <ContactCardName>Sung Kim</ContactCardName>14      <ContactCardAddress />15    </ContactCard>16  );17}

It requires to create seprate component for ContactCardName or ContactCardAddress, each of which accepts its own children function.

This is where things get interesting.
You can specify which "slot" you want to insert the child content into!

ContactCard.svelte

1<style>2  .missing {3  }4</style>5
6<article class="contact-card">7  <h2>8    <slot name="name">9      <span class="missing">Unknown name</span>10    </slot>11  </h2>12
13  <div class="address">14    <slot name="address">15      <span class="missing">Unknown address</span>16    </slot>17  </div>18
19  <div class="email">20    <slot name="email">21      <span class="missing">Unknown email</span>22    </slot>23  </div>24</article>

As shown in the previous section, each named slots contain fallbacks.

The calling component specifies the slot in the child component

App.svelte

1<script>2  import ContactCard from "./ContactCard.svelte";3</script>4
5<ContactCard>6  <span slot="name">Sung</span>7  <span slot="email">Sung@sung.com</span>8</ContactCard>

c. Slot props

https://svelte.dev/tutorial/slot-props

Passing data from slot to the parent component, one needs to declare the exposed state (via slot) while declaring the component

You don't declare a variable in the parent component but just sorta like "bind" using let.

Hovering.svelte: a component containg a slot.

1<script>2  let hovering;3
4  const enter = () => (hovering = true);5  const leave = () => (hovering = false);6</script>7
8<div on:mouseenter="{enter}" on:mouseleave="{leave}">9  <slot hovering="{hovering}"></slot>10  <!-- or use the hsort hand -->11  <!-- <slot hovering></slot> -->12</div>

To access hovering in the parent component, use let as mentioend before.

Parent.svelte

1<script>2  import Hoverable from "./Hoverable.svelte";3</script>4
5<Hoverable let:hovering="{hovering}">6  <div class:active="{hovering}">7    {#if hovering}8    <p>I am being hovered upon.</p>9    {:else}10    <p>Hover over me!</p>11    {/if}12  </div>13</Hoverable>

Note that hovering variable is not declared in the script but could be used inside Hovering.

15. Context API

a. setContext and getContext

https://svelte.dev/tutorial/context-api

Svelte's Context API is similar to that of React;
Only decendant child components can access context data using getContext expoed via setContext in the parent.

store is more like Zustand where state is avaiable anywhere in the component hierachy.

Difference between React & Svelte Context API is that, React's API is declarative using a markup, Svelte imperative, using setContext during component initialization.

React

1function App() {2  return <Context.Provider value={value}>children can access context value here</Context.Provider>;3}

16. Special elements

a. <svelte:self>

https://svelte.dev/tutorial/svelte-self

To recursively refer the current component.

There is a typo in the documentation so filed an issue: https://github.com/sveltejs/svelte/issues/5044
Update: "a file" refers to the current file, not the File component. So the documentation is correct. Clsoed the issue.

b. <svelte:component>

https://svelte.dev/tutorial/svelte-component

Use <svelte:component this={component}> to load a component dynamically.

To pass props, pass it to <svelte:component>.

<svelte:component text="custom text" this="{selected.component}" />

text is then passed to selected.component (not documented in the tutorial just found out by mistake).

Make sure that the dynamic component accepts the prop.

e.g.) RedThing.svelte

1<style>2  strong {3    color: red;4  }5</style>6
7<script>8  export let text = "red thing";9</script>10
11<strong>{text}</strong>

c. <svelte:window>

https://svelte.dev/tutorial/svelte-window

It's a declarative way to add events to window object.

d. <svelte:window> bindings

https://svelte.dev/tutorial/svelte-window-bindings

Turns out, you can also bind to some of window's properties, not just events.

e. <svelte:body>

https://svelte.dev/tutorial/svelte-body

This lets you bind events declaratively in the document.body.

f. <svelte:head>

https://svelte.dev/tutorial/svelte-head

Injecting content inside <html><head>.
No need for react-helmet like 3rd party library.

g. <svelte:options>

https://svelte.dev/tutorial/svelte-options

advanced Svelte compiler options.
Most notably, you can specify immutibility to optimize component render in a list.

17. Module context

a. Sharing code

https://svelte.dev/tutorial/sharing-code

This looks like a "static" variable avaiable throughout the all the instances of a component.
Possibly a prototype value.

b. Exports

https://svelte.dev/tutorial/module-exports

Exporting within module level script can be imported from another Svelte component.

18. Debugging

a. The @debug tag

https://svelte.dev/tutorial/debug

The better "console.log" :p


Photo by William Krause on Unsplash