Reputation: 978
I have a very simple SvelteKit (1.0) app, you can check the repo here : https://github.com/joaopedrocoelho/sveltecounter , and deployed with Amplify at https://main.d2u56jyg3xz1fe.amplifyapp.com/
whenever I add a counter after deleting another one the nameList array in my svelte store an empty element is added in the array, I fixed it by filtering the array but I'm wondering what's the root of the problem, I made a video of the issue:
+page.svelte
<script lang="ts">
import Counter from '../components/Counter.svelte';
import { sum, nameList } from '../store';
let counters = [Counter];
let currentSum: number;
let currentNameList: string[] = [];
sum.subscribe((value) => {
currentSum = value;
});
//this is where the empty element is first logged
nameList.subscribe((value) => {
console.log('nameList', value);
currentNameList = value.filter(Boolean);
});
const addCounter = () => {
counters = [...counters, Counter];
};
</script>
<div class="flex m-auto flex-col max-w-sm">
<h1 class="text-6xl text-center mb-6">Multiple Counter</h1>
{#each counters as Counter, i}
<Counter index={i} />
{/each}
<button
on:click={addCounter}
class="max-w-sm w-full mt-2 m-auto text-center
bg-green-400 rounded text-white cursor-pointer"
>
new counter
</button>
<p class="flex ">title list: {currentNameList}</p>
<p class="flex">sum of count: {currentSum}</p>
</div>
Counter.svelte
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { sum, nameList } from '../store';
export let index: number = 0;
let count: number = 0;
let name: string = 'new';
let counterRef: HTMLDivElement;
const increment = () => {
sum.update((n) => n + 1);
count++;
};
const decrement = () => {
if (count > 0) {
sum.update((n) => n - 1);
count--;
}
};
const reset = () => {
sum.update((n) => n - count);
count = 0;
};
const removeCounter = () => {
sum.update((n) => n - count);
counterRef.remove();
nameList.update((n) => {
let newList = [...n];
newList.splice(index, 1);
return newList;
});
};
onMount(() => {
nameList.update((n) => {
let newList = [...n];
newList[index] = name;
return newList;
});
});
onDestroy(() => {
nameList.update((n) => {
let newList = [...n];
newList.splice(index, 1);
return newList;
});
});
const onChangeList = (event: Event) => {
nameList.update((n) => {
let newList = [...n];
newList[index] = (event.target as HTMLInputElement).value;
return newList;
});
};
</script>
<div
class="max-w-sm bg-gray-100
shadow-lg m-auto
flex relative
items-center mb-4 py-2"
bind:this={counterRef}
>
<input value={name} on:input={onChangeList} class="text-gray-600 mx-4 px-1 w-32" />
<span class="text-lg font-bold px-4">{count}</span>
<div class="ml-auto flex">
<button on:click={increment} class="btn bg-red-500 rounded-l">+</button>
<button on:click={decrement} class="btn bg-blue-500">-</button>
<button on:click={reset} class="btn bg-yellow-500 rounded-r">0</button>
<button
on:click={removeCounter}
class="bg-white text-gray-600
rounded-full h-8 w-8 mx-4
flex items-center justify-center font-bold px-1 py-0">X</button
>
</div>
</div>
<style>
.btn {
--text-opacity: 1;
color: #fff;
color: rgba(255, 255, 255, var(--text-opacity));
padding-top: 0.25rem;
padding-bottom: 0.25rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
font-size: 1.125rem;
}
</style>
Upvotes: 0
Views: 111
Reputation: 7721
The problem is that when a counter is removed, its count is substracted from the sum, the counterRef
removed from the Dom (not recommended) and the corresponding entry removed from nameList
- but the counters
the #each
block is built upon stays the same.
So after what you show in the video - adding two, removing one, adding another - the index of the lastly added element is one more than expected and hence the empty entry REPL with logs
set
,subscribe
and update
are usually not necessary inside a component because the $ store prefix
can be used instead
Here's a different approach REPL
counters
(tutorial)sum
and namesList
(docs-tutorial)bind:property
(tutorial) to write values of name
and count
to the corresponding object in counters
(values will be initialized with default values set inside component export let count= 0
)createEventDispatcher
(docs-tutorial) to trigger removal of a counter<script>
import Counter from './Counter.svelte';
import { counters, nameList, sum } from './store';
</script>
<h1>Multiple Counter</h1>
{#each $counters as c, i (c)}
<Counter bind:name={c.name}
bind:count={c.count}
on:remove="{() => counters.remove(c)}"
/>
{/each}
<button on:click={counters.add} >
new counter
</button>
<pre>
title list: {$nameList.join(', ')}
sum of count: {$sum}
counters: {JSON.stringify($counters, null, 2)}
</pre>
<script>
import { onMount, onDestroy } from 'svelte';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
export let count = 0;
export let name = 'new';
const increment = () => count++
const decrement = () => {
if (count > 0) {
count--;
}
};
const reset = () => count = 0
</script>
<div>
<input bind:value={name} />
<span>{count}</span>
<div>
<button on:click={increment}>+</button>
<button on:click={decrement} disabled={count === 0}>-</button>
<button on:click={reset}>0</button>
<button on:click={() => dispatch('remove')} >X</button>
</div>
</div>
import {writable, derived} from 'svelte/store'
function createCountersStore() {
const { subscribe, set, update } = writable([{}]);
return {
subscribe,
set,
add() {
update(value => [...value, {}])
},
remove(counter) {
update(value => value.filter(c => c !== counter))
}
};
}
export const counters = createCountersStore()
export const nameList = derived(counters, $counters => $counters.map(counter => counter.name))
export const sum = derived(counters, $counters => {
return $counters.reduce((sum, counter) => sum += counter.count, 0 )
})
Upvotes: 3