Categories
Svelte

Storing Shared State in a Svelte App

Svelte is an up and coming front end framework for developing front end web apps.

It’s simple to use and lets us create results fast.

In this article, we’ll look at how to store shared states in a Svelte store.

Writable Stores

We can add a writable store to store data that are shared by multiple components.

For instance, we can write the following to create a store and use it in a component:

App.svelte :

<script>
  import { count } from "./stores.js";

  let countValue;

  const unsubscribe = count.subscribe(value => {
    countValue = value;
  });

  const increment = () => {
    count.update(n => n + 1);
  };
</script>

<button on:click={increment}>Increment</button>
<p>{countValue}</p>

stores.js :

import { writable } from "svelte/store";

export const count = writable(0);

In the code above, we created a shared writable state in stores.js called count .

Then we subscribed to it to get the latest value and set it to count_value .

We can also call the set method on count to reset the value as follows:

App.svelte :

<script>
  import { count } from "./stores.js";

  let countValue;

  const unsubscribe = count.subscribe(value => {
    countValue = value;
  });

  const increment = () => {
    count.update(n => n + 1);
  };

  const reset = () => {
    count.set(0);
  };
</script>

<button on:click={increment}>Increment</button>
<button on:click={reset}>Reset</button>
<p>{countValue}</p>

In the code above, we run count.set(0) when we click on Reset to set the count state to 0.

We have to call unsubscribe on the store when the component is destroyed so that we can free up the resources used for the subscription of states.

For instance, we can modify the example above as follows to do that:

App.svelte :

<script>
  import { count } from "./stores.js";
  import { onDestroy } from "svelte";

  let countValue;

  const unsubscribe = count.subscribe(value => {
    countValue = value;
  });

  const increment = () => {
    count.update(n => n + 1);
  };

  onDestroy(unsubscribe);
</script>

<button on:click={increment}>Increment</button>
<p>{countValue}</p>

The code above calls unsubscribe the function in the destroy portion of the component lifecycle so that the count state is no longer subscribed to.

We can replace the store state subscription with the $ . For instance, we can shorten the above example to:

<script>
  import { count } from "./stores.js";
  import { onDestroy } from "svelte";

  const increment = () => {
    count.update(n => n + 1);
  };
</script>

<button on:click={increment}>Increment</button>
<p>{$count}</p>

We eliminated the code to subscribe and unsubscribe from the count store and the component still works the same as before.

Read Only Stores

We can make a store that’s read only with the readable function.

For instance, we can write the following code to make a counter state that’s subscribed to by a component as follows:

stores.js :

import { readable } from "svelte/store";

export const count = readable(0, set => {
  let count = 0;
  const interval = setInterval(() => {
    set(count);
    count++;
  }, 1000);

  return () => {
    clearInterval(interval);
  };
});

App.svelte :

<script>
  import { count } from "./stores.js";
</script>

<p>{$count}</p>

In the code above, we call the readable function in the store. In the second argument, we pass in a callback with the set function as the parameter, which is called to update the store’s value.

We did that in the setInterval callback. Then we return another function, which is used to clean up the code when the store is unsubscribed to.

When we run the code, we see a number that’s incremented by 1 every second on the screen.

Store with Derived State

We can call the derived function to create a store that’s derived from another state.

For instance, we can create a store that emits a value that’s double the count state that we have above as follows:

stores.js :

import { readable, derived } from "svelte/store";

export const count = readable(0, set => {
  let count = 0;
  const interval = setInterval(() => {
    set(count);
    count++;
  }, 1000);

  return () => {
    clearInterval(interval);
  };
});

export const doubleCount = derived(count, $count => $count * 2);

App.svelte :

<script>
  import { count, doubleCount } from "./stores.js";
</script>

<p>{$count}</p>
<p>{$doubleCount}</p>

In the code above, we have the doubleCount store, which is created by calling the derived function with the count store passed in as the first argument and a callback which returns a value that we want to derive from the value emitted by count as the second argument.

Then we see a number that’s incremented by 1 every second and another that’s incremented by 2 every second.

Store Bindings

We can bind directly to the value of the store if the store is writable.

For instance, we can use bind:value to bind to the store to the inputted value directly and manipulate the store’s value as follows:

store.js :

import { writable } from "svelte/store";

export const name = writable("");

App.svelte :

<script>
  import { name } from "./stores.js";
</script>

<input bind:value={$name}>
<button on:click="{() => $name += '.'}">
  Add period
</button>
<p>{$name}</p>

In the code above, we change the value of $name as we type in something in the input, and also when we click Add period.

Then we display the $name store’s value in the p element.

Conclusion

A store is handy for storing shared states. We make stores that are read-only, writable or are derived from another store.

Categories
Svelte

Svelte Component Lifecycle Hooks

Svelte is an up and coming front end framework for developing front end web apps.

It’s simple to use and lets us create results fast.

In this article, we’ll look at the lifecycle hooks of a Svelte component.

Component Lifecycle

Every Svelte component has a lifecycle. It starts when it’ created and ends when it’s destroyed.

There’re a handful of functions that allows us to run code at key moments of the lifecycle.

The most frequently used is onMount . It’s run after the component is first rendered to the DOM.

We can add a onMount handler to our component as follows:

<script>
  import { onMount } from "svelte";

  let joke = { value: { joke: "" } };

  onMount(async () => {
    const res = await fetch(`https://api.icndb.com/jokes/20`);
    joke = await res.json();
  });
</script>

<p>{joke.value.joke}</p>

The code above calls the Chuck Norris API to get a joke when the component loads.

Then we display it after it’s loaded.

onDestroy

onDestroy ‘s callback is called when the when the component is being destroyed.

Cleanup code and anything else that runs when the component can go into onDestroy ‘s callback.

For instance, we can write:

<script>
  import { onDestroy } from "svelte";

  let seconds = 0;
  const interval = setInterval(() => seconds++, 1000);

  onDestroy(() => clearInterval(interval));
</script>

<p>Seconds: {seconds}</p>

In the code above, we call setInterval , which returns an interval ID object which we set as the value of interval .

Then when the component is being destroyed, we call clearInterval to clear the interval object from memory by passing interval into clearInterval in the onDestroy callback.

beforeUpdate and afterUpdate

beforeUpdate runs immediately before the DOM has been updated.

afterUpdate is used for running code once the DOM is in sync with our data.

For instance, if we want to scroll to the bottom of the div if the div overflows the window height, we can write the following code:

<script>
  import { beforeUpdate, afterUpdate } from "svelte";
  let div;
  let autoscroll;
  let arr = [];
  setTimeout(() => {
    arr = new Array(100).fill("foo");
  }, 100);

  beforeUpdate(() => {
    autoscroll = div && div.offsetHeight + div.scrollTop > div.scrollHeight - 20;
  });

  afterUpdate(() => {
    if (autoscroll) {
      window.scrollTo(0, div.scrollHeight);
    }
  });
</script>

<div bind:this={div}>
{#each arr as a}
<p>{a}</p>
{/each}
</div>

The code:

div && div.offsetHeight + div.scrollTop > div.scrollHeight - 20

checks if the height of the div is bigger than the scroll height, which includes the height that’s not visible on the screen.

Then in the afterUpdate callback, we can scroll to the bottom of the div if there’s overflow as returned by the boolean above.

Tick

tick is a function that returns a promise that resolves as soon as any pending state changes have been applied to the DOM.

When the component state is invalidated in Svelte, it doesn’t update the DOM immediately. It waits until the next microtask to see if there’re any changes that need to be applied.

This allows the batching of updates.

When we select a range of text in the text area and hit the tab key to toggle its case, then the current selection is cleared and the cursor jumps to the end.

We can retain the selection after the tab key is pressed by in a Textarea by writing the following code:

<script>
  import { tick } from "svelte";

let text = "";
  async function handleKeydown(event) {
    if (event.which !== 9) return;
    event.preventDefault();

    const { selectionStart, selectionEnd, value } = this;
    const selection = value.slice(selectionStart, selectionEnd);

    const replacement = /[a-z]/.test(selection)
      ? selection.toUpperCase()
      : selection.toLowerCase();

    text =
      value.slice(0, selectionStart) + replacement + value.slice(selectionEnd);
    await tick();
    this.selectionStart = selectionStart;
    this.selectionEnd = selectionEnd;
  }
</script>

<textarea bind:value={text} on:keydown={handleKeydown}></textarea>

In the code above we have the handleKeydown to see if the tab key is pressed.

We keep the text from changing by setting the text to the same content as before.

If it is, then we set the selectionStart and selectionEnd after tick is called to retain the selection.

Conclusion

We can use the lifecycle methods to run the code we want during a stage of the lifecycle.

There’s one for when the component loaded, one for when a component is destroyed, 2 for DOM updates, and one for when the component’s state is updated.

Categories
Svelte

Sharing Code Between Svelte Component Instances with Module Context

Svelte is an up and coming front end framework for developing front end web apps.

It’s simple to use and lets us create results fast.

In this article, we’ll look at how to share variables between component instances with module context.

Module Context

Svelte components can contain code that is shared between multiple component instances.

To do this, we can declare the script block with context='module' . The code inside it will run once, when the module first evaluates, rather than when a component is instantiated.

For instance, we can use it to create multiple audio elements that can only have one audio element play at a time by stopping the ones that aren’t being played as follows:

App.svelte :

<script>
  import AudioPlayer from "./AudioPlayer.svelte";
</script>

<AudioPlayer
  src="https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3"
/>

<AudioPlayer
  src="https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_1MG.mp3"
/>

<AudioPlayer
  src="http://www.hochmuth.com/mp3/Haydn_Cello_Concerto_D-1.mp3"
/>

AudioPlayer.svelts :

<script context="module">
  let current;
</script>

<script>
  export let src;

  let audio;
  let paused = true;

  const stopOthers = () =>{
    if (current && current !== audio) current.pause();
    current = audio;
  }
</script>

<article>
  <audio
    bind:this={audio}
    bind:paused
    on:play={stopOthers}
    controls
    {src}
  ></audio>
</article>

In the code above, we have the AudioPlayer component which has the code that’s shared between all instances in:

<script context="module">
  let current;
</script>

This lets us call pause on any instance of the audio element that isn’t the one that’s being played as we did in the stopOthers function. stopOthers is run when we click play on an audio element.

Then we included the AudioPlayer instances in the App.svelte component.

Therefore, when we click play on one of the audio elements, we pause the other ones.

Exports

Anything that’s exported from a context='module' script block becomes an export of the module itself. For instance, we can create a button to stop all the audio elements in AudioPlayer that is currently playing as follows:

App.svelte :

<script>
  import AudioPlayer, { stopAll } from "./AudioPlayer.svelte";
</script>

<button on:click={stopAll}>Stop All</button>

<AudioPlayer
  src="https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_700KB.mp3"
/>

<AudioPlayer
  src="https://file-examples.com/wp-content/uploads/2017/11/file_example_MP3_1MG.mp3"
/>

<AudioPlayer
  src="http://www.hochmuth.com/mp3/Haydn_Cello_Concerto_D-1.mp3"
/>

AudioPlayer.svelte :

<script context="module">
  let elements = new Set();

  export const stopAll = () => {
    elements.forEach(element => {
      element.pause();
    });
  };
</script>

<script>
  import { onMount } from 'svelte';
  export let src;
  let audio;

  onMount(() => {
    elements.add(audio);
    return () => elements.delete(audio);
  });
</script>

<article>
  <audio
    bind:this={audio}
    controls
    {src}
  ></audio>
</article>

In the code above, we add all the audio element instances to the elements set which we created in the contex='module' block as each AudioPlayer component is mounted.

Then we export the stopAll function, which has all the audio element instances from elements , and we call pause on all of them by looping through them and calling pause .

In App.svelte , we import the stopAll function from AudioPlayer and then call it when we click the Stop All button.

Then when we click play on one or more audio elements, they’ll all be paused when we click the Stop All button.

Note that we can’t have a default export because the component is the default export.

Conclusion

We can use the module context script block to add script blocks that are shared between all instances of a component.

If we export functions inside module context script blocks, we can import the function and then call it.

Categories
Svelte

More Complex Bindings with Svelte

Svelte is an up and coming front end framework for developing front end web apps.

It’s simple to use and lets us create results fast.

In this article, we’ll look at how to use the bind directive outside of input elements.

Media Elements

We can use the bind directive with audio or video elements.

For instance, we can bind to various attributes of audio or video elements as follows:

<script>
let time = 0;
let duration = 0;
let paused = true;
</script>

<video
  bind:currentTime={time}
  bind:duration
  bind:paused
  controls
  src='https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_30mb.mp4'
>
</video>
<p>{time}</p>
<p>{duration}</p>
<p>{paused}</p>

In the code above, we used the bind directive to bind to currentTime , duration , and paused properties of the DOM video element.

They’re all updated as the video is being played or paused if applicable.

6 of the bindings for the audio and video elements are read only, they’re:

  • duration — length of the video in seconds
  • buffered — array of {start, end} objects
  • seekable — array of {start, end} objects
  • played — array of {start, end} objects
  • seeking — boolean
  • ended — boolean

2 way binds are available for:

  • currentTime — current point in the video in seconds
  • playbackRate — how fast to play the video
  • paused
  • volume — value between 0 and 1

Video also has the videoWidth and videoHeight bindings.

Dimensions

We can bind to the dimensions of an HTML element.

For instance, we can write the following to adjust the font size of a piece of text as follows:

<script>
  let size = 42;
  let text = "hello";
</script>

<input type=range bind:value={size}>

<div >
  <span style="font-size: {size}px">{text}</span>
</div>

There’re also read-only bindings for width and height of elements, which we can use as follows:

<script>
 let w;
 let h;
</script>

<style>
  div {
    width: 100px;
    height: 200px;
  }
</style>

<div bind:clientWidth={w} bind:clientHeight={h}>
  {w} x {h}
</div>

bind:clientWidth and bind:clientHeight binds the width and height to w and h respectively.

This Binding

We can use the this bind to set the DOM element object to a variable.

For instance, we can use the this binding to access the canvas element as follows:

<script>
  import { onMount } from "svelte";

  let canvas;

  onMount(() => {
    const ctx = canvas.getContext("2d");
    ctx.beginPath();
    ctx.moveTo(20, 20);
    ctx.lineTo(20, 100);
    ctx.lineTo(100, 100);
    ctx.stroke();
  });
</script>

<canvas
  bind:this={canvas}
  width={500}
  height={500}
></canvas>

In the code above, we put our canvas manipulation code in the onMount callback so that we only run the code when the canvas is loaded.

We have bind:this={canvas} to bind to the canvas element so that we can access it in the onMount callback.

In the end, we get an L-shaped line drawn on the screen.

Component Bindings

We can bind to component props with bind .

For example, we can write the following code to get the value from the props as follows:

App.svelte :

<script>
  import Input from "./Input.svelte";
  let val;
</script>

<Input bind:value={val} />
<p>{val}</p>

Input.svelte :

<script>
export let value;
</script>

<input bind:value={value} />

In the code above, we have value bound to the input element. Then since we passed val in as the value of the value prop and used the bind directive, val in App will be updated as value in Input is updated.

So when we type into the input box, the value will also be shown in the p element below.

Conclusion

We can bind to media elements when we use bind with some property names.

Using the this binding, we can get the DOM element in the script tag and call DOM methods on it.

Component props can also be used with bind , then we get 2-way binding between parent and child via the props.

Categories
Svelte

Special Elements in Svelte

Svelte is an up and coming front end framework for developing front end web apps.

It’s simple to use and lets us create results fast.

In this article, we’ll look at special Svelte elements and how we can use them.

svelte:component

We can use the svelte:component element to load components dynamically. It takes a this prop which accepts a component object to load.

For instance, we can use it as follows:

<script>
  import Foo from "./Foo.svelte";
  import Bar from "./Bar.svelte";

let component = Foo;
  const toggle = () => {
    component = component === Foo ? Bar : Foo;
  };
</script>

<button on:click={toggle}>Toggle</button>

<svelte:component this={component}/>

In the code above, we have the toggle to set component to the component we want to load by toggling between Foo and Bar as the value of component .

Then we pass component into the this prop to load the component.

svelte:window

We can use the svelte:window component to add listeners to the window object.

For instance, we can use it as follows:

<script>
  let key;
  const handleKeydown = e => {
    key = e.keyCode;
  };
</script>

<p>{key}</p>

<svelte:window on:keydown={handleKeydown}/>

The code above attaches the handleKeydown to the window object. handleKeydown gets the keyCode from the event object e and then sets the value to key .

key is then displayed in a p element. Then when we press on keys then we see the key code of the key that’s pressed.

We can also add event modifiers like preventDefault to svelte:window like any other DOM element.

Also, we can bind to certain properties of window such as the following:

  • innerWidth
  • innerHeight
  • outerWidth
  • outerHeight
  • scrollX
  • scrollY
  • online — an alias for window.navigator.onLine

All properties except scrollX and scrollY are read-only. For instance, we can write the following to display those values on the screen:

<script>
  let innerWidth;
  let innerHeight;
  let outerWidth;
  let outerHeight;
  let scrollX;
  let scrollY;
  let online;
</script>

<p>{innerWidth}</p>
<p>{innerHeight}</p>
<p>{outerWidth}</p>
<p>{outerHeight}</p>
<p>{scrollX}</p>
<p>{scrollY}</p>
<p>{online}</p>

<svelte:window
  bind:innerWidth
  bind:innerHeight
  bind:outerWidth
  bind:outerHeight
  bind:online
  bind:scrollX
  bind:scrollY
/>

The values are automatically updated since we used bind to bind the values to the variables.

svelte:body

svelte:body element allows us to listen for events in that’s fired on document.body .

For instance, we can use it to listen to mouse movements on the body element as follows:

<script>
  let clientX;
  let clientY;
  const handleMousemove = e => {
    clientX = e.clientX;
    clientY = e.clientY;
  };
</script>

<p>{clientX}</p>
<p>{clientY}</p>
<svelte:body on:mousemove={handleMousemove} />

The code above watches for mouse movements in the body adding the on:mousemove directive to svelte:body and setting the handleMousemove handler function as the value.

Then we get the mouse position by getting the clientX and clientY from the event object e and setting them to variables.

Finally, we display those values by interpolating those variables in the p elements.

Therefore, when we move the mouse inside the body, then we see the mouse position coordinates displayed.

svelte:head

The svelte:head elements let us insert elements inside the head of our document. For instance, we can use it to reference an external CSS file by writing:

<svelte:head>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
   crossorigin="anonymous">
</svelte:head>

<h1 class="text-primary">foo</h1>

In the code above, we just put the link tag for Bootstrap CSS in between the svelte:head tags. Then we can see the word foo with the color blue.

In server-side rendering mode, svelte:head ‘s contents are returned separated from the rest of the HTML.

svelte:options

svelte:options lets us specify compiler options. We can change the following options by passing them as props:

  • immutable={true} — Make the compiler do simple referential checks to determine if values have changed
  • immutable={false} — The default choice. Svelte will be more conservative about whether or not mutable objects have changed
  • accessors={true} — Adds getters and setters for the component’s props
  • accessors={false} — The default choice.
  • namespace="..." — The namespace where this component will be used
  • tag="..." — The name to use when compiling a component as a custom element.

For example, we can write the following to set the immutable option to true :

<svelte:options immutable={true} />

<h1>foo</h1>

Conclusion

Svelte’s special components let us access various parts of the DOM like window and document.body without hassle.

svelte:component lets us dynamically load components.

Also, we can reference the svelte:options component to set various app-wide options.