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.)
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)}>