Passing Parent Method to Child in Svelte
Asked Answered
K

4

15

As the title suggests, I am trying to pass a method from a parent component to a child component.

For example,

App.html

<div>
  <TodoItem
     done={todo.done}
     toggle={toggle}
  />
</div>
<script>
 import TodoItem from './TodoItem.html';
 export default {
   components: {
     TodoItem,
   },
   methods: {
     toggle(index) {
       console.log(index);
     },
   },
 };
</script>

TodoItem.html

<div>
  <button on:click="toggle(0)"></button>
</div>
<script>
 export default {
   methods: {
     toggle(index) {
       // a guess. this works if you pass in console.log
       this.options.data.toggle(index)
     },
   },
 };
</script>

The desired functionality is that TodoItem calls the parent's method with its data.

This example breaks, the console logs TypeError: this.options.data.toggle is not a function.

Kelcie answered 5/6, 2018 at 14:30 Comment(0)
A
15

It's possible to pass methods down to child components, but it's a little awkward. A more idiomatic approach is to fire an event from the child component and listen for that event from the parent component:

App.html

<div>
  <TodoItem
    {todo}
    on:toggle="toggle(todo)"
  />
</div>
<script>
  import TodoItem from './TodoItem.html';
  export default {
    components: {
      TodoItem,
    },
    methods: {
      toggle(todo) {
        todo.done = !todo.done;
        const { todos } = this.get();
        this.set({ todos });
     }
   }
 };
</script>

TodoItem.html

<div>
  <button on:click="fire('toggle')">{todo.description}</button>
</div>

If you need to pass an event up through multiple levels of components you can just refire the event...

<TodoItem on:toggle="fire('toggle', event)">...</TodoItem>

...but there's a shorthand for doing so that means the same thing:

<TodoItem on:toggle>...</TodoItem>
Aholah answered 5/6, 2018 at 17:18 Comment(2)
What if the child is not a component but a simple <button>?Elephant
This is easier in Svelte 3 as Ujjwal Kumar Gupta pointed out. Maybe it's worth updating this answer with the Svelte 3 example along the Svelte 2 version since this has already been accepted as the correct answer.Untidy
A
30

Seems like "fire" was part of svelte v2 but in svelte v3 it's changed with createEventDispatcher

e.g -

child.svelte

<script>
    import { createEventDispatcher } from 'svelte';

    const dispatch = createEventDispatcher();

    function sayHello() {
        dispatch('message', {
            text: 'Hello!'
        });
    }
</script>

<button on:click={sayHello}>
    Click to say hello
</button>

parent.svelte

<script>
    import Inner from './child.svelte';

    function handleMessage(event) {
        alert(event.detail.text);
    }
</script>

<Inner on:message={handleMessage}/>

for more info - please visit : https://svelte.dev/tutorial/component-events

Argentous answered 16/12, 2019 at 11:3 Comment(0)
A
15

It's possible to pass methods down to child components, but it's a little awkward. A more idiomatic approach is to fire an event from the child component and listen for that event from the parent component:

App.html

<div>
  <TodoItem
    {todo}
    on:toggle="toggle(todo)"
  />
</div>
<script>
  import TodoItem from './TodoItem.html';
  export default {
    components: {
      TodoItem,
    },
    methods: {
      toggle(todo) {
        todo.done = !todo.done;
        const { todos } = this.get();
        this.set({ todos });
     }
   }
 };
</script>

TodoItem.html

<div>
  <button on:click="fire('toggle')">{todo.description}</button>
</div>

If you need to pass an event up through multiple levels of components you can just refire the event...

<TodoItem on:toggle="fire('toggle', event)">...</TodoItem>

...but there's a shorthand for doing so that means the same thing:

<TodoItem on:toggle>...</TodoItem>
Aholah answered 5/6, 2018 at 17:18 Comment(2)
What if the child is not a component but a simple <button>?Elephant
This is easier in Svelte 3 as Ujjwal Kumar Gupta pointed out. Maybe it's worth updating this answer with the Svelte 3 example along the Svelte 2 version since this has already been accepted as the correct answer.Untidy
L
4

This worked for me.

From the SOURCE

Useful when the component can't live without a parent, and the parent can't live without the component

App.svelte

<script>
    import Child from './Child.svelte'
    
    const handleSubmit = value => {
        console.log(value)
    }
</script>

<Child {handleSubmit}/>

Child.svelte

<script>
    export let handleSubmit
    let value = ''
    
    const onSubmit = e => {
        e.preventDefault()
        handleSubmit(value)
    }
</script>

<form on:submit={onSubmit}>
    <input type="text" bind:value/>
</form>

Another solution is to use context

Useful when all the children and grandchildren components may or may not call the function. The function allows all children and grandchildren components to update the state / make changes to the common parent component.

REPL

App.svelte

<script>
    import { getContext, setContext } from 'svelte';
    import Child1 from './Child1.svelte';
    import Child2 from './Child2.svelte';
    
    let counter = 10;
    
    setContext('counter', { increment, decrement });
    
    function increment(delta) {
        counter += delta;
    }
    function decrement(delta) {
        counter -= delta;
    }
</script>

<Child1 />
<Child2 />

<button on:click={() => { increment(10); }}>
    Increment x10
</button>

<div>{counter}</div>

Child1.svelte

<script>
    import { getContext } from 'svelte';
    
    const { increment } = getContext('counter');
</script>

<button on:click={() => increment(1)}>Increment</button>

Child2.svelte

<script>
    import { getContext } from 'svelte';
    
    const { decrement } = getContext('counter');
</script>

<button on:click={() => decrement(1)}>Decrement</button>
Linette answered 8/1, 2022 at 13:52 Comment(1)
By using the | preventDefault modifier it was also possible to directly call the passed function without the need of the intermediate onSubmit like this <form on:submit|preventDefault={handleSubmit(value)}>Lula
I
0

Since Svelte 3, you can pass down the parent method directly to the child component just like in React. From this answer:

There is no significant difference between callback props and events. The only difference is semantic. Callback props used to be quite tricky to use in svelte 2 so events used to be the preferred method. The redesign in v3 made callback props much simpler to use but we also still have events. So having both is partly historic. Events are a little bit easier to forward up the tree but this is a pretty minor benefit, if at all.

Inadvisable answered 15/2, 2023 at 11:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.