I have an app that stores items into locations in localStorage and then displays the items in HTML.
One of the reasons i wanted to use Svelte was for reactive variables, but whenever I attempt to use a reactive variable that changes whenever localStorage.current_items changes, the ItemList variable doesn't change.
The only way I could get it to work is by using setInterval but that is not a great way to do it. How can I make it so that ItemList changes properly when the localStorage.current_items string changes.
<script lang="ts">
import {
getData,
createItem,
createLocation,
closeItem,
} from './lib/database.js';
import LocationSelector from './lib/LocationSelector.svelte';
import { flip } from 'svelte/animate';
import { writable } from 'svelte/store';
let DB = getData();
// load items from localstorage.items
let ItemList = [];
let Location;
setInterval(() => {
Location = localStorage.current_items;
ItemList = JSON.parse(localStorage.current_items).items;
}, 500);
console.log(ItemList);
let newItem = '';
let filter_showClosed = false;
function addNewItem(e) {
e.preventDefault();
console.log(newItem);
const newItemInput = document.querySelector(
'#newItemInput'
) as HTMLInputElement;
createItem(JSON.parse(Location).id, newItem);
newItem = '';
}
function newItemKeyDown(e) {
if (e.keyCode === 13) {
addNewItem(e);
}
}
</script>
<LocationSelector />
<div class="app">
<input
type="text"
id="newItemInput"
bind:value={newItem}
placeholder="Add a new item"
on:keydown={newItemKeyDown}
/>
<button
id="filter_showClosed"
data-active="false"
on:click={function () {
filter_showClosed = !filter_showClosed;
let el = document.getElementById('filter_showClosed');
if (filter_showClosed) {
el.innerHTML = 'Hide closed';
el.dataset.active = 'true';
} else {
el.innerHTML = 'Show closed';
el.dataset.active = 'false';
}
}}>Show closed</button
>
<!-- <button
id="deleteClosed"
on:click={function () {
let it = items;
for (let i = 0; i < it.length; i++) {
if (it[i].closed == true) {
it.splice(i, 1);
}
}
items = it;
sort_items(items);
}}>Delete all closed</button
> -->
<div class="list">
{#each ItemList as item, index (item.id)}
<div class="item {item.closed}" animate:flip={{ duration: 100 }}>
{#if item.closed == false || (filter_showClosed == true && item.closed == true)}
<div>
<img
src="/up.svg"
class="item-icon"
class:closed={item.closed == true}
alt="move item up in priority"
on:click={function () {
// increaseLevel({ item });
}}
/>
{item.name} ({index})
</div>
<div>
{#if item.closed == false}
<img
src="/close.svg"
class="item-icon"
alt="close item"
on:click={function () {
console.log(Location.id);
closeItem(JSON.parse(Location).id, item.id);
}}
/>
{/if}
</div>
{/if}
</div>
{/each}
</div>
</div>
<style>
</style>
I tried using this writeable method, but that didn't work either as the variable still didn't change.
import { writable } from 'svelte/store';
const ItemList = writable([]);
let Location = {};
let newItem = '';
let filter_showClosed = false;
function addNewItem(e) {
e.preventDefault();
console.log(newItem);
const newItemInput = document.querySelector(
'#newItemInput'
) as HTMLInputElement;
createItem(Location.id, newItem);
newItem = '';
}
function newItemKeyDown(e) {
if (e.keyCode === 13) {
addNewItem(e);
}
}
// Update the Location object with the current value of localStorage.current_items as an object
Location = JSON.parse(localStorage.current_items);
// Update the ItemList store with the new location's items
ItemList.set(Location.items);
You should use a store that fully wraps the access to localStorage
.
Something like:
function localStorageStore(key, initial) {
const value = localStorage.getItem(key)
const store = writable(value == null ? initial : JSON.parse(value));
store.subscribe(v => localStorage.setItem(key, JSON.stringify(v)));
return store;
}
Reading and writing is just a regular store, but on initial load the value comes from the storage and on setting the value, it is also written to storage.
In Svelte 5 you can use a $state
with an $effect
.
To make it easy to pass around the value, even if it is a primitive, it should be wrapped (see e.g. also the svelte/reactivity/window
module).
function localStorageState(key, initial) {
const value = localStorage.getItem(key)
const state = $state({
current: value == null ? initial : JSON.parse(value)
});
$effect(() => {
localStorage.setItem(key, JSON.stringify(state.current));
});
return state;
}
<script>
// ...
const name = localStorageState('name', 'world');
</script>
<h1>Hello {name.current}!</h1>
<input bind:value={name.current} />
(If the function is defined in a JS/TS file, the name has to include .svelte.
, so e.g. local-storage.svelte.js
.)
These are sketch implementations for demonstration of the principles only. They e.g. do not handle SSR, which has no access to localStorage
and have no built-in way of handling version changes (if you want to change the format of the value later, which is stored on each client and thus not easily accessible).