arzidava

Calling function in child components with Svelte

See this Repl for a working example of this code


Svelte allows us to expose properties using export let prop to make them available in the parent and easily read them, update them, even bind to them using two-way binding. But nothing stops us from exposing a function in the same way. The question that arises from that is however how we do call this function ? And why would we want to this in the first place ? In this article I would like to show you the how and explore a bit into the second question of the why.

How to expose a function

The first thing we should do is to create a component that exposes a function, this is actually the easiest part since it would be exactly as you expect it to be. In the code below we have a very simple component that will show a given name, with a function that allows us to toggle a css class that will turn that name red.

<script>
    let marked = false

	export let name
	export const toggle = () => marked = !marked
</script>

<style>
	.marked { color: red }
</style>

<div class:marked>{name}</div>

How to use the function

When using this component we can then bind a reference to it to a previously declared variable using bind:this (see also the (Svelte Docs)[https://svelte.dev/docs#bind_element]) and use this variable in a function call.

<script>
    import Child from './Child.svelte'
    let child
</script>
<Child bind:this="{child}" />
<button on:click="{() => child.toggle()}">Click</button>

Note that in the above code we cannot use the shorthand on:click="{child.toggle}" because initially child is undefined, always use the arrow syntax in this case (unless you call a function handler).

Use within a {#each} loop

If we generate our child components within a loop we can no longer do this, after all the code bind:this="{mychild}"} would only bind the very last of those components to mychild leaving all the others unbound. Luckily this can easily be done by not binding to variable but instead binding to an element in an array instead.

<script>
	import Child from './Child.svelte'
	let animalNames = ['Rabbit', 'Squirrel', 'Hamster']

    let animals = []
</script>
{#each animalNames as name, i}
	<Child {name} bind:this="{animals[i]}" />
{/each}

To call a function on one of those components we just refer to the element within the array animals[i].toggle() or we can even loop over them with animals.forEach(animal => animale.toggle())

Use with a JavaScript Object

Ofcourse we are not limited to use arrays, it is very well possible that for some reason you might want to use a JavaScript Object instead (this would then more act like a lookup table), the principle here is the same.

<script>
	import Child from './Child.svelte'
	let animalNames = ['Rabbit', 'Squirrel', 'Hamster']

    let animals = {}
</script>
{#each animalNames as name}
	<Child {name} bind:this="{animals[name]}" />
{/each}

In this case you would need the key to get the actual element: animals['Rabbit'].toggle()

Why would we want to do this ?

There can be plenty of reason to want to this, but most often we are trying to do expose a certain behaviour in a read-only manner. A very simple example would be an stylized input field that we want to be able to focus programmatically from outside the component.

<script>
    let _input

    export let label
    export let value
    export const focus = () => _input.focus()
</script>
<label>
    <span>{label}</span>
    <input type="text" bind:this="{_input}" bind:value>
</label>