I’m going through the Svelte tutorial. I’m in the ContextAPI part, and I don’t understand why and how onMount is called again inside the addItem function, in addition to the first call, in Canvas.svelte. Isn’t it supposed to be called only once during component initialization?
Canvas.svelte:
<script>
import { setContext, afterUpdate, onMount, tick } from 'svelte';
export let width = 100;
export let height = 100;
let canvas, ctx;
let items = new Set();
let scheduled = false;
onMount(() => {
ctx = canvas.getContext('2d');
});
setContext('canvas', {
addItem
});
function addItem(fn) {
onMount(() => {
items.add(fn);
return () => items.delete(fn);
});
afterUpdate(async () => {
if (scheduled) return;
scheduled = true;
await tick();
scheduled = false;
draw();
});
}
function draw() {
ctx.clearRect(0, 0, width, height);
items.forEach(fn => fn(ctx));
}
</script>
<canvas bind:this={canvas} {width} {height}>
<slot />
</canvas>
<style>
canvas {
width: 100%;
height: 100%;
}
</style>
App.svelte:
<script>
import Canvas from './Canvas.svelte';
import Square from './Square.svelte';
// we use a seeded random number generator to get consistent jitter
let seed = 1;
function random() {
seed *= 16807;
seed %= 2147483647;
return (seed - 1) / 2147483646;
}
function jitter(amount) {
return amount * (random() - 0.5);
}
</script>
<div class="container">
<Canvas width={800} height={1200}>
{#each Array(12) as _, c}
{#each Array(22) as _, r}
<Square
x={180 + c * 40 + jitter(r * 2)}
y={180 + r * 40 + jitter(r * 2)}
size={40}
rotate={jitter(r * 0.05)}
/>
{/each}
{/each}
</Canvas>
</div>
<style>
.container {
height: 100%;
aspect-ratio: 2 / 3;
margin: 0 auto;
background: rgb(224, 219, 213);
filter: drop-shadow(0.5em 0.5em 1em rgba(0, 0, 0, 0.1));
}
</style>
Square.svelte:
<script>
import { getContext } from 'svelte';
export let x;
export let y;
export let size;
export let rotate;
getContext('canvas').addItem(draw);
function draw(ctx) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rotate);
ctx.strokeStyle = 'black';
ctx.strokeRect(-size / 2, -size / 2, size, size);
ctx.restore();
}
</script>
>Solution :
Isn’t it supposed to be called only once during component initialization?
You can call it as many times as you want, it just adds an event handler to the list. But it does have to happen during component initialization.
addItem is being passed to the context and called during the initialization of the child components Square, so it refers to the mounting of that, not the parent Canvas.
In general it can be useful to use those hooks in functions to enable work that has its own initialization and cleanup logic attached to it.