markiyanm
markiyanm

Reputation: 348

Svelte value not updating on screen when passing a value to a function

A little confused on why the below isn't working. I have tried this where I don't pass an argument to the function and can get the UI to update but if I pass the argument in and do the same exact stuff to it it updates it in the code and I can console.log it out but the UI is not updated.

This Works:

<script>
   import { text, toggle_class } from "svelte/internal";

   let line1 = {name:"line 1", display:"some text here", originalText:"some init text", visible:true};

function toggleView(){ 
    line1.visible = !line1.visible;
    if(!line1.visible) line1.display = "*************"
    else line1.display = line1.originalText
};
</script>

<main>
    <button on:click="{toggleView}">{line1.display}</button>
</main>

This does NOT work:

<script>
   import { text, toggle_class } from "svelte/internal";

   let line1 = {name:"line 1", display:"some text here", originalText:"some init text", visible:true};

function toggleView(field){ 
    field.visible = !field.visible;
    if(!field.visible) field.display = "*************"
    else field.display = field.originalText
};
</script>

<main>
    <button on:click="{toggleView(line1)}">{line1.display}</button>
</main>

I think it might be because I'm assigning this to a local variable from Svelte's point of view but I'm not sure how else I would call a function to make this reusable since I would be doing this for a bunch of lines.

Any help would be appreciated.


UPDATE - Solved

Below works based on Thomas' answer below:

<script>
    import { text, toggle_class } from "svelte/internal";
    var lines = {
        line1: {
            name: "line 1",
            display:"some text here",
            originalText:"some init text",
            visible:true
        }
    };
    
    function toggleView(field) {
        return function() {
            lines[field].visible = !lines[field].visible;
            if (!lines[field].visible) {
                lines[field].display = "*************";
            } else {
                lines[field].display = lines[field].originalText;
            }
        }
    }
</script>

<main>
    <button on:click="{toggleView('line1')}">{lines.line1.display}</button>
</main>

Upvotes: 1

Views: 6064

Answers (2)

Thomas Hennes
Thomas Hennes

Reputation: 9939

When you set on:click to toggleView(line1), the function is executed right away, just as if you'd set it to toggleView() instead of toggleView in your other, working example.

You have two ways to fix this. The most common approach is to turn your on:click handler into a lambda/anonymous function:

<button on:click="{() => toggleView(line1)}">{line1.display}</button>

The other, less common approach is to modify your handler function and turn it into a curried function:

function toggleView(field) {
    return function() { 
        field.visible = !field.visible;
        if(!field.visible) field.display = "*************"
        else field.display = field.originalText
    };
};

in which case on:click={toggleView(field)} will return a function that will be bound to the click handler, and that function will actually execute when the button is clicked.

Upvotes: 3

rixo
rixo

Reputation: 25001

In raw HTML + JS, an inline event handler like onclick is actually a bit of code that will be executed when the event happens. In particular, if you use a function in your handler, you must call it:

Example (not Svelte):

<button onclick="toggleView()" />

In contrast, in Svelte, a event handler is expected to be a function, that will be called when the event happens.

OK because toggleView is a function:

<button on:click={toggleView} />

Not OK because it calls toggleView() every time the markup is rendered (i.e. when reactive vars change), and _the return value of togggleView() is bound to the click event:

<button on:click={toggleView()} />

If you need to pass local arguments to an event handler, then you need to wrap the function to still get what you want (that the whole is called when the event happens, not when the markup is rerenred):

Example, OK + argument:

<button on:click={() => toggleView(line1)} />

Note that here, the handler is () => toggleView(line1), not toggleView(line1).

Upvotes: 0

Related Questions