18
votes

I have a component called WastedTime.svelte with a value wastedTime. There's also a function to change the value to 50 (in my real code, this does an animation but this is a reduced test case for Stack Overflow).

To allow the child function to be called from a parent, I have used <script context="module"> per the Svelte docs:

<script context="module">
    var wastedTime = 0;
    export function changeNumber(){
        console.log('changing number')
        wastedTime = 50
    }
</script>

<script>
    // Putting 'var wastedTime = 0' here doesn't work either
</script>


<h1>Wasted time: {wastedTime}</h1>

The parent calls the function in the child from onMount:

<script>

    import { onMount } from 'svelte';
    import WastedTime, {changeNumber } from './WastedTime.svelte';

    onMount(() => {
        changeNumber()
    });
</script>

<WastedTime />

The problem is that since wastedTime is referred to in <script context="module">, it can't seem to change wastedTime. The exported function runs, but wastedTime stays at 0.

Copy of this on Svelte REPL

I have tried: - Putting var wastedTime = 0 in <script context="module"> - Putting var wastedTime = 0 in <script>

Neither works.

How can I export a function from a Svelte component that changes a value in the component?

3

3 Answers

23
votes

<script context="module"> isn't reactive — changes to variables inside this block won't affect individual instances (unless you were changing a store value, and every instance was subscribed to that store).

Instead, export the changeNumber function directly from the instance, and get a reference to it with bind:this:

WastedTime.svelte

<script>
    var someNumber = 0;
    export function changeNumber(){
        console.log('changing number')
        someNumber = 56
    }
</script>

<h1>Wasted time: {someNumber}</h1>

App.svelte

<script>
    import { onMount } from 'svelte';
    import WastedTime from './WastedTime.svelte';

    let wastedTimeComponent;

    onMount(() => {
        wastedTimeComponent.changeNumber()
    });
</script>

<WastedTime bind:this={wastedTimeComponent} />

Demo here: https://svelte.dev/repl/f5304fef5c6e43edb8bf0d25d634f965

2
votes

Well, that's a damn good question!

My First Attempt:

I've tried @Rich Harris answer (export a function from within the component, bind it when using the component with onMount event listener). The concept is indeed solid, but my problem was a little bit more complex - I've tried to pass an event with parameter to be invoked from the outside. And yes - this parameter is forged using {#await}.. so... no luck with that approach


My Second Attempt:

After learning more about how svelte handles events (I'm a newbie to svelte and learning as I progress), I've found this great article. In short: let's make our component truly-event oriented! In your question - you actually trying to implement onload event... so.. why shouldn't we do just that?

component.svelte

<script>
  import { createEventDispatcher, onMount } from 'svelte';
  const dispatch = createEventDispatcher();

  function doSomething() { console.log('wowza!') }
  
  // wait for onMount to trigger, then dispatch event named "load"
  onMount(() => dispatch('load', 
  { 
    data: 'yay',
    doSomething
  }));
</script>

app.svelte

<script>
  import { Component } from './component.svelte';

  function component_load(event)
  {
    console.log(event.detail.data);
    event.detail.doSomething();
  }
</script>
<Component on:load={component_load} />

So - yep, I've learned something today! Also:

  • This is a more elegant way to code (event-driven)
  • Component expose itself with proper usage of Svelte events life-cycle
  • dispatch can be triggered in response to other events - letting you constuct a complete life-cycle for your component

AMAZING!

0
votes

Simplified version of Rich's answer:

App.svelte

<script>
import Time from './Time.svelte';

let timeComponent;
let time;
</script>

<Time bind:this={timeComponent} bind:time />

<h1>Spent time: {time} ms</h1>

{#if timeComponent}
  <button on:click={() => timeComponent.spendTime() }>Click me</button>
{:else}
  Loading...
{/if}

Time.svelte

<script>
export var time = 0;
export function spendTime() {
  time += 50;
}
</script>

The key here is export function.