Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing Parent Method to Child in Svelte

Tags:

svelte

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.

like image 698
pxljoy Avatar asked Jun 05 '18 14:06

pxljoy


3 Answers

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

like image 123
Ujjwal Kumar Gupta Avatar answered Nov 02 '22 18:11

Ujjwal Kumar Gupta


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>
like image 27
Rich Harris Avatar answered Nov 02 '22 17:11

Rich Harris


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>
like image 34
francis Avatar answered Nov 02 '22 18:11

francis