I am building a Svelte component to display a list whose items can be added to a selection. The selection itself is a store:
selectionStore.js
import {writable} from 'svelte/store';
function createSelectionStore() {
const { subscribe, set, update } = writable(JSON.parse(localStorage.getItem("selection")) || {});
function remove(selection, itemId, itemType) {...}
function add(selection, item) {...}
return {
subscribe,
remove: (itemId, itemType) => update(selection => remove(selection, itemId, itemType)),
add: (item) => update(selection => add(selection, item)),
toggleSelection: (item) => update(selection => {
if (selection[item.type]?.[item.id]) {
return remove(selection, item.id, item.type);
}
return add(selection, item);
}),
isSelected: (selection, item) => {
return selection[item.type]?.hasOwnProperty(item.id) || false;
},
length: (selection, itemType) => {
return Object.keys(selectedItems[itemType]).length ?? 0
},
};
}
export const selectionStore = createSelectionStore();
The store is then imported in the components:
RecordList.svelte
<script>
import Record from "./Record.svelte";
import { selectionStore } from "./selectionStore.js";
export let records = [];
$: selectionLength = selectionStore.length(false);
</script>
<p>Number of selected items: {selectionLength}</p>
{#if records.length !== 0}
<div>
{#each records as item (item.id)}
<Record {item}/>
{/each}
</div>
{/if}
Record.svelte
<script>
import { selectionStore } from './selectionStore.svelte';
export let item;
$: isSelected = selectionStore.isSelected(item);
</script>
<div class="item">
{item.item}
<button on:click={() => selectionStore.toggleSelection(item)}>
{isSelected ? 'Remove from' : 'Add to'} selection
</button>
</div>
The issue I am facing is that isSelected
and selectionLength
are not reactive, even though toggleSelection()
seem to work. I know that I am not structuring my store properly but I cannot find the right way to do it.
Here what I ended doing, thank you very much for all your help!
selectionStore.js
import {writable} from 'svelte/store';
function createSelectionStore() {
const { subscribe, set, update } = writable(JSON.parse(localStorage.getItem("selection")) || {});
function remove(selection, itemId, itemType) {...}
function add(selection, item) {...}
return {
subscribe,
remove: (itemId, itemType) => update(selection => remove(selection, itemId, itemType)),
add: (item) => update(selection => add(selection, item)),
toggleSelection: (item) => update(selection => {
if (selection[item.type]?.[item.id]) {
return remove(selection, item.id, item.type);
}
return add(selection, item);
}),
isSelected: derived(selection, $selection =>
item => $selection[item.type]?.hasOwnProperty(item.id) || false
),
nbSelected: derived(selection, $selection =>
itemType => Object.keys(selectedItems[itemType]).length ?? 0
),
};
}
export const selectionStore = createSelectionStore();
RecordList.svelte
<script>
import Record from "./Record.svelte";
import { selectionStore } from "./selectionStore.js";
const { nbSelected } = selectionStore;
export let records = [];
</script>
<p>Number of selected items: {$nbSelected("Records")}</p>
{#if records.length !== 0}
<div>
{#each records as item (item.id)}
<Record {item}/>
{/each}
</div>
{/if}
Record.svelte
<script>
import { selectionStore } from './selectionStore.svelte';
const { isSelected } = selectionStore;
export let item;
</script>
<div class="item">
{item.item}
<button on:click={() => selectionStore.toggleSelection(item)}>
{$isSelected(item) ? 'Remove from' : 'Add to'} selection
</button>
</div>
Everything that should be reactive needs to be a store.
In the case of these functions, you need a derived
that depends on the store holding the selection data.
const selection = writable(...);
const { subscribe, set, update } = selection;
const isSelected = derived(selection, $selection =>
item => $selection[item.type]?.hasOwnProperty(item.id) || false
);
...
return { subscribe, isSelected, ... }
<script>
import { selectionStore } from './selectionStore.js';
export let item;
const { isSelected } = selectionStore; // required for accessing store via $
$: itemSelected = $isSelected(item); // or just inline in template
</script>
(This will be a lot more simple in Svelte 5 with runes rather than stores. Functions that access state will be reactive by default.)