Home > Enterprise >  How to toggle CSS class based on a value in a Svelte store
How to toggle CSS class based on a value in a Svelte store

Time:09-05

I am new to Svelte and I am struggling with the stores. What I am trying is to individually toggle the background color of an element in an each loop. I figured it out by simply giving each div block its own html id and then addressing it via getElementById().

My question is, is there a more elegant way to do it?

Like binding the individual css classes of each div-block to the store values? I tried thing like class:backgound={todo.done} but that only worked on the initial render. Once the value was updated, it didn't work.

import { writable } from 'svelte/store'

 export const list = writable([
        {
        text: 'test1',
        done: false,
        id:1,
         },
          {
        text: 'test2',
        done: true,
        id:2,
         },
           {
        text: 'test3',
        done: false,
        id:3,
         },

    ]);

There seems to be something I am missing when it comes to how and when the store is accessed. What I want to avoid is to store the props in a variable because I never know how many elements there will be in the store.

<script>
        import {list} from './stores.js'
  
        const toggleDone = (todo) => {
            todo.done = !todo.done
            let elem = document.getElementById(todo.id);
            elem.style.backgroundColor = todo.done ? 'red' : '';
            console.log($list)
        }
   
        let completedTodos = $list.filter((todo) => todo.completed).length
        let totalTodos = $list.length
</script>

<style>
    .list {
        display: flex;
        justify-content: center;
        padding-right: 10px;
        margin: 10px;
        flex-wrap: wrap;
        
    }
    .active {
        background-color: red;
    }
</style>

<h2 id="">{completedTodos} out of {totalTodos} items completed</h2>

{#each $list as todo, index (todo.id)}
    <div  id="{todo.id}"  >
    <form action="">
        <input type="checkbox" checked={todo.done} on:click={toggleDone(todo)}>
    </form>
    {todo.text} id: {todo.id}
    </div>
{/each}


CodePudding user response:

The problem is that you are using a function to toggle the item, or rather that the function is fully disconnected from the store: Svelte does not know that the item in the function is a part of the store, the function also could be called with other values.

The easiest fix is to toggle the item inline:

<div  class:active={todo.done}>
    <label>
        <input type="checkbox" checked={todo.done}
            on:click={() => todo.done = !todo.done}>
        {todo.text} id: {todo.id}
    </label>
</div>

Or rather, you could just use bind:

<input type="checkbox" bind:checked={todo.done}>

(Also, please use label elements like this, so it is more accessible. Clicking the label will also toggle the item.)

REPL

If the function is implemented in a way that it writes to the store, state will update correctly as well. For list this usually can be done most conveniently via the index, e.g.

function toggle(index) {
    $list[index].done = !$list[index].done
}
<input type="checkbox" checked={todo.done}
       on:click={() => toggle(index)}>
  • Related