109

Binding a function to a button is easy and straightforward:

<button on:click={handleClick}>
    Clicks are handled by the handleClick function!
</button>

But I don't see a way to pass parameters (arguments) to the function, when I do this:

<button on:click={handleClick("parameter1")}>
    Oh no!
</button>

The function is called on page load, and never again.

Is it possible at all to pass parameters to function called from on:click{}?


EDIT:

I just found a hacky way the proper way to do it (see comments). Calling the function from an inline handler works.

<button on:click={() => handleClick("parameter1")}>
    It works...
</button>
Félix Paradis
  • 5,165
  • 6
  • 40
  • 49
  • Thats what even the docs have not explicitly mentioned. But yes thats the way for now i think.. Until they come up with a different solution.. – Manish Oct 07 '19 at 04:41
  • 65
    This isn't a hack, it's *how it works*. It's explicitly mentioned in the tutorial https://svelte.dev/tutorial/inline-handlers – Rich Harris Oct 07 '19 at 13:12
  • 6
    Thanks for your comments! This "hacky way" of doing it is not so bad after all, but I would dare say that the docs and tutorial are not _very_ explicit about this. Maybe it's just me, though. – Félix Paradis Oct 07 '19 at 15:54
  • 12
    For what it's worth, the reason `on:click{() => clickHandler(param)}` is, as Rich said above, "how it works", is because one needs to pass a _reference_ to the function needing to be executed. By calling `on:click{clickHandler(param)}`, you are executing the function immediately. That's not what you want. – solidstatejake Aug 23 '20 at 23:41

9 Answers9

108

TL;DR

Just wrap the handler function in another function. For elegancy, use arrow function.


You have to use a function declaration and then call the handler with arguments. Arrow function are elegant and good for this scenario.

WHY do I need another function wrapper?

If you would use just the handler and pass the parameters, how would it look like?

Probably something like this:

<button on:click={handleClick("arg1")}>My awesome button</button>

But remember, handleClick("arg1") this is how you invoke the function instantly, and that is exactly what is happening when you put it this way, it will be called when the execution reaches this line, and not as expected, ON BUTTON CLICK...

Therefore, you need a function declaration, which will be invoked only by the click event and inside it you call your handler with as many arguments as you want.

<button on:click={() => handleClick("arg1", "arg2")}>
    My awesome button
</button>

As @Rich Harris (the author of Svelte) pointed out in the comments above: This is not a hack, but is what the documentation shows also in their tutorials: https://svelte.dev/tutorial/inline-handlers

OmG3r
  • 1,658
  • 13
  • 25
V. Sambor
  • 12,361
  • 6
  • 46
  • 65
  • for me it is not work – romanown Feb 11 '21 at 10:40
  • 2
    `on:click={() => handleClick("arg1", "arg2")}` is the way to go – Khaled.K Oct 13 '21 at 12:51
  • 3
    At a philosophical level, I guess having it this way also makes it as close to vanilla JavaScript as possible. The idea of "when in doubt, do it like JavaScript" holds here (and of course they've done some work behind the hood to make it efficient). In fact, if I hadn't bothered looking it up I'd probably have intuitively gone for this solution anyway :P – Hippo Sep 16 '22 at 15:04
34

Rich has answered this in a comment, so credit to him, but the way to bind parameters in a click handler is as follows:

<a href="#" on:click|preventDefault={() => onDelete(projectId)}>delete</a>
<script>
function onDelete (id) {
  ...
}
</script>

To provide some extra detail for people who also struggle with this, and it should be in the docs if it isn't, you can also get the click event in such a handler:

<a href="#" on:click={event => onDelete(event)}>delete</a>
<script>
function onDelete (event) {
  // if it's a custom event you can get the properties passed to it:
  const customEventData = event.detail

  // if you want the element, you guessed it:
  const targetElement = event.target
  ...
}
</script>

Svelte docs/tutorial: inline handlers

OmG3r
  • 1,658
  • 13
  • 25
Antony Jones
  • 2,387
  • 1
  • 15
  • 18
7

There is no clear way mentioned in the documentation and your solution will work but is indeed not very elegant. My own preferred solution is to use currying in the script block itself.

const handleClick = (parameter) => () => {
   // actual function
} 

And in the HTML

<button on:click={handleClick('parameter1')>
   It works...
</button>

Beware of currying

As mentioned in the comments, currying has its pitfalls. The most common one that in the above example handleClick('parameter1') will not be fired when clicking but rather on rendering, returning a function that in turn will be fired onclick. This means that this function will always use 'parameter1' as it's argument.

Therefore using this method would only be safe if the used parameter is a constant of some kind and will not change once it's rendered.

This would bring me to another point:

1) If it's a constant used a parameter, you could as well use a seperate function

const handleParameter1Click = () => handleClick('parameter1');

2) If the value is dynamic but available within the component, this could still be handled with a standalone function:

let parameter1;
const handleParameter1Click = () => handleClick(parameter1);

3) If the value is dynamic but not available from the component because this is dependent on some kind of scope (eg: a list of items rendered in a #each block) the 'hacky' approach will work better. However I think it would be better to in that case have the list-elements as a component themselves and fall back to case #2

To conclude: currying will work under certain circumstance but is not recommended unless you are very well aware and careful about how to use it.

Stephane Vanraes
  • 14,343
  • 2
  • 23
  • 41
  • 5
    Don't do this! Just create an inline function (`on:click={() => handleClick('parameter1')}`) – Rich Harris Oct 07 '19 at 13:13
  • 1
    Just realised that's the proposal you were responding to. The reason I advise against the currying approach is twofold — first, it's less immediately clear what's happening (people tend to assume that `handleClick('parameter1')` is what happens *when the click happens*, incorrectly. Secondly, it means that the handler needs to be rebound whenever parameters change, which is suboptimal. Currently, there's a bug, meaning that doesn't work anyway https://svelte.dev/repl/a6c78d8de3e2461c9a44cf15b37b4dda?version=3.12.1 – Rich Harris Oct 07 '19 at 13:20
  • 1
    True, I was not aware of that bug never encountered it myself. Then again in the codebase I am working on we never pass parameter like this, they are almost always objects and that seems to work: https://svelte.dev/repl/72dbe1ebd8874cf8acab86779455fa17?version=3.12.1 – Stephane Vanraes Oct 07 '19 at 14:05
  • I updated my answer with some currying pitfalls and other reflections. Thanks for the input – Stephane Vanraes Oct 08 '19 at 06:31
  • Ah yep, it'll work with objects, as long as the reference itself doesn't change (and the underlying bug will get fixed in due course as well) – Rich Harris Oct 08 '19 at 14:44
3

I got it working with this:

<a href="#" on:click|preventDefault={onDelete.bind(this, project_id)}>delete</a>

function onDelete(id) {
}
chovy
  • 72,281
  • 52
  • 227
  • 295
3

Pug solution

Assign with !=. Yes, assign with !=. Weird as hell. Example Foo(on:click!='{() => click(i)}'). This is per documentation for svelte-preprocess (a necessary transpiler to include templates in pug):

Pug encodes everything inside an element attribute to html entities, so attr="{foo && bar}" becomes attr="foo &amp;&amp; bar". To prevent this from happening, instead of using the = operator use != which won't encode your attribute value [...] This is also necessary to pass callbacks.

Personally, I will use a util functions for this, because assign with != bothers me too much.

// util fn
const will = (f, v) => () => f(v);
// in pug template we can again assign with =
Foo(on:click='{will(click, i)}')
n-smits
  • 685
  • 7
  • 20
1

Here it is with a debounce method and event argument:

<input type="text" on:keydown={event => onKeyDown(event)} />


const onKeyDown = debounce(handleInput, 250);

async function handleInput(event){
    console.log(event);
}
chovy
  • 72,281
  • 52
  • 227
  • 295
0

At risk of necroposting, check out https://svelte.dev/repl/08aca4e5d75e4ba7b8b05680f3d3bf7a?version=3.23.1

sortable table. Note the passing of parameters to the header click handling function in a magical way. No idea why this is so. Auto-currying maybe?

emperorz
  • 429
  • 3
  • 9
  • 1
    the `sort` function in that REPL returns a function, and that function handles the click. when the component is loaded, `sort` is called twice, with the name of the column, and returns two functions, one which will sort by id, and one which will sort by val. – Antony Jones Apr 13 '21 at 13:58
0

I'm using:

{#each data as item}    
   <li on:click={handle(item)}>{item.name}</li>
{/each}

...and it is not running the function when it renders, it works on click.

Yuzem
  • 29
  • 2
  • 1
    If you can prove this works for you in the REPL I would be shocked. Here's a REPL showing that the functions will indeed run on initial render and not on click at all. https://svelte.dev/repl/741340eb0ec34ae89601a1f069747c22?version=3.44.1 – JHeth Nov 06 '21 at 09:20
  • You are right, I dont remember why I wrote that it worked back then. – Yuzem Nov 07 '21 at 14:22
  • Happens to me all the time, things stop working or change completely so often. – JHeth Nov 07 '21 at 21:15
  • I've actually seen this work before too, and I *dont* know why. – Shayne May 05 '23 at 05:48
0

Sorry to necro, but it appears to work in this particular situation. I don't know enough to understand why, but I thought it was relevant since even Rich says it shouldn't. I couldn't find any reason after searching that made this case unique.

REPL

tetshi
  • 56
  • 5