Home > Software engineering >  Hover on object entries triggers unnecessary reactivity in Svelte
Hover on object entries triggers unnecessary reactivity in Svelte

Time:06-17

I have an object that I loop over with an each block. When I hover, I temporarily change a property on the actual object entry and use it to assign a class. This works fine, but it also causes other reactivity to be triggered unnecessarily often.

<script>
    let things = [
        { id: 1, name: 'apple' },
        { id: 2, name: 'banana' },
        { id: 3, name: 'carrot' },
        { id: 4, name: 'doughnut' },
        { id: 5, name: 'egg' },
    ];
    
    function makePretty(somethings) {
        const prettyThings = somethings.map(thing => thing.name).join(' ');
        console.log(prettyThings);
        return prettyThings;
    }
    
    $: prettyThings = makePretty(things)
</script>

<ul>
{#each things as thing (thing.id)}
    <li 
            class:hover={thing.hover}
            on:mouseenter={() => (thing.hover = true)}
            on:mouseleave={() => (thing.hover = false)}
    >
        {thing.name}
    </li>
{/each}
</ul>

<style>
    .hover {
        background-color: cyan;
    }
</style>

REPL: https://svelte.dev/repl/c658cccd0bc2471a8f9c4758387340c5?version=3.48.0 n the console, you can see how often the reactivity on the prettyThings is triggered when you move over the list with your mouse.

I do realise that this is expected behaviour, as the 'things'-object is effectively changed on every mouseenter and mouseleave. And thus the prettyThings is called every time.

What would be an ideomatic way to apply a hover to a 'row' from an array of objects, without changing the object itself? In doing so, the object should still be live modifiable by other operations (e.g. add, delete, ...).

CodePudding user response:

you can use css only to achieve this:

<script>
    let things = [
        { id: 1, name: 'apple' },
        { id: 2, name: 'banana' },
        { id: 3, name: 'carrot' },
        { id: 4, name: 'doughnut' },
        { id: 5, name: 'egg' },
    ];
    
    function makePretty(somethings) {
        const prettyThings = somethings.map(thing => thing.name).join(' ');
        console.log(prettyThings);
        return prettyThings;
    }
    
    $: prettyThings = makePretty(things)
</script>

<ul>
{#each things as thing (thing.id)}
    <li>
        {thing.name}
    </li>
{/each}
</ul>

<style>
    li:hover {
        background-color: cyan;
    }
</style>

REPL: https://svelte.dev/repl/9590df9f09334adda571e6cf8ddb34ce?version=3.48.0

CodePudding user response:

One way to do this, would be to split the rendering into a separate component.

<script>
    export let thing;
</script>

<li class:hover={thing.hover}
    on:mouseenter={() => (thing.hover = true)}
    on:mouseleave={() => (thing.hover = false)} >
    {thing.name}
</li>

<style>
    .hover {
        background-color: cyan;
    }
</style>
{#each things as thing (thing.id)}
    <ItemDisplay {thing} />
{/each}

REPL

Maybe there are better approaches, though.

  • Related