Ragnar learns svelte

opening a thread here just so I can ramble without bothering people in #chit-chat or #off-topic
114 Replies
Rägnar O'ock
Rägnar O'ockOP2mo ago
https://svelte.dev/docs/svelte/compiler-errors#constant_binding just got the most unhelpful error I've seen in a while...
No description
ἔρως
ἔρως2mo ago
change const to let wait nevermind
Rägnar O'ock
Rägnar O'ockOP2mo ago
well it did fix it... but... I don't like the implications of it...
ἔρως
ἔρως2mo ago
i know what you mean
Rägnar O'ock
Rägnar O'ockOP2mo ago
there's no writable computed in svelte ?!?!
<script lang="ts">
export interface Todo {
id: string;
label: string;
doneOn: Date | null;
}

const { todo = $bindable() }: {todo: Todo} = $props();

let isChecked = $derived(todo.doneOn !== null);
$effect(() => { todo.doneOn = isChecked ? new Date() : null; });
</script>

<label for="{todo.id}">{todo.label}</label>
<input type="checkbox" name="{todo.id}" id="{todo.id}" bind:checked={isChecked}>
<script lang="ts">
export interface Todo {
id: string;
label: string;
doneOn: Date | null;
}

const { todo = $bindable() }: {todo: Todo} = $props();

let isChecked = $derived(todo.doneOn !== null);
$effect(() => { todo.doneOn = isChecked ? new Date() : null; });
</script>

<label for="{todo.id}">{todo.label}</label>
<input type="checkbox" name="{todo.id}" id="{todo.id}" bind:checked={isChecked}>
please someone tell me there's a way to do it declaratively... this irks me @b1mind sorry to ping, but I need to know and I'm too lazy to search for myself xD damn it I scrolled down in the doc... https://svelte.dev/docs/svelte/@const why would anyone make this a thing ? 😭
Rägnar O'ock
Rägnar O'ockOP2mo ago
webstorm integration is not up-to-date or am I doing something wrong ? 'cause it's working :/
No description
Rägnar O'ock
Rägnar O'ockOP2mo ago
<script lang="ts">
// imports
const todos = $state<Todo[]>([]);

let newTodo = $state('');
const addTodo: Attachment = form =>
on(form, 'submit', () => {
if (newTodo === '') {return;}
todos.push({id: crypto.randomUUID(), label: newTodo, doneOn: null});
newTodo = '';
})
</script>

<form {@attach addTodo}>
<label for="newTodo">add todo</label>
<input type="text" name="newTodo" id="newTodo" bind:value={newTodo}>

<input type="submit" value="addTodo">
</form>

{#each todos as _,index}
<ul>
<TodoItem bind:todo={todos[index]}></TodoItem>
</ul>
{/each}
<script lang="ts">
// imports
const todos = $state<Todo[]>([]);

let newTodo = $state('');
const addTodo: Attachment = form =>
on(form, 'submit', () => {
if (newTodo === '') {return;}
todos.push({id: crypto.randomUUID(), label: newTodo, doneOn: null});
newTodo = '';
})
</script>

<form {@attach addTodo}>
<label for="newTodo">add todo</label>
<input type="text" name="newTodo" id="newTodo" bind:value={newTodo}>

<input type="submit" value="addTodo">
</form>

{#each todos as _,index}
<ul>
<TodoItem bind:todo={todos[index]}></TodoItem>
</ul>
{/each}
Rägnar O'ock
Rägnar O'ockOP2mo ago
"it's just JS guys" you lied to me B1... how dare you!
No description
Rägnar O'ock
Rägnar O'ockOP2mo ago
ah... I created an infinite loop of errors... woups
ἔρως
ἔρως2mo ago
how?
Rägnar O'ock
Rägnar O'ockOP2mo ago
I tried to display the todos in 2 lists, one filtered on the done and one on the todo and... it exploded I guess it's because of the $effect I used here https://discord.com/channels/436251713830125568/1429163039453745213/1429166444301647872 but so far I got no clue and if it's that... I don't know how to fix the issue ok so now I'm stuck... how do i do the following Vue thing in svelte ?
const checkedAt = ref<Date|null>(null);
const isChecked = computed({
set: isNowChecked => isChecked.value = isNowChecked ? new Date() : null,
get: () => checkedAt.value !== null
});
const checkedAt = ref<Date|null>(null);
const isChecked = computed({
set: isNowChecked => isChecked.value = isNowChecked ? new Date() : null,
get: () => checkedAt.value !== null
});
basically, convert the boolean "is checked ?" to the less boolean "if it is checked when was it checked ?" i.e. writatable computed I don't like this...
<script lang="ts">
export interface Todo {
id: string;
label: string;
doneOn: Date | null;
}

const { todo = $bindable() }: {todo: Todo} = $props();

let isChecked = $derived(todo.doneOn !== null);
const toggle = () => todo.doneOn = isChecked ? new Date() : null
</script>

<li>
<input
type="checkbox"
name="{todo.id}"
id="{todo.id}"
bind:checked={isChecked}
onchange={toggle}
>
<label for="{todo.id}">{todo.label}</label>
</li>
<script lang="ts">
export interface Todo {
id: string;
label: string;
doneOn: Date | null;
}

const { todo = $bindable() }: {todo: Todo} = $props();

let isChecked = $derived(todo.doneOn !== null);
const toggle = () => todo.doneOn = isChecked ? new Date() : null
</script>

<li>
<input
type="checkbox"
name="{todo.id}"
id="{todo.id}"
bind:checked={isChecked}
onchange={toggle}
>
<label for="{todo.id}">{todo.label}</label>
</li>
I have a let that I should not set because if I do the template complains and an update function that is decoralated from the derived it will indirectly update someone please tell me I'm doing things horribly wrong pleeeease Imma start throwin hands... https://svelte.dev/docs/kit/$lib
Rägnar O'ock
Rägnar O'ockOP2mo ago
import aliases are an anti pattern just like logic in the template
CDL
CDL2mo ago
I’m excited to see you try react if this is how you are with svelte 🤣
Rägnar O'ock
Rägnar O'ockOP2mo ago
you want me to murder someone ? that's no nice... forthe someone imma try to add the server bits tomorow, but no promises
Ganesh
Ganesh2mo ago
I think this would be easier if you just had a done property on the state and used that property directly for bind Or pass the value without bind if using derived and use event handler Oh todo isn't a state it's prop hmmm. Yeah I'm not deep enough in svelte to answer this
Rägnar O'ock
Rägnar O'ockOP2mo ago
I want it to be a date so I can sort them by that date
Ganesh
Ganesh2mo ago
I meant keep the doneOn and add an extra done property just for the checkbox input Derived value just doesn't make sense to be a bind property to me since that implies you're directly changing it instead of it reacting to changes in state
Rägnar O'ock
Rägnar O'ockOP2mo ago
From what I understand of how the doc explains it you are indeed not supposed to do it like this. But i don't like having intermediary variables, I feel like it just adds noise I guess the correct way would be to have a $state<boolean>, bind that on the checkbox and have an $effect do the conversion into date when that boolean switches. But that means doing the comparison with null in 2 places, one in the effect and one to initialize the state And... I don't like it one bit Didn't get the motivation to work on it today, I'll try again tomorrow after coming back from work
Ganesh
Ganesh2mo ago
Can you just not use bind:value and simply use value?
Ganesh
Ganesh2mo ago
not sure if svelte playground is shareable but i tried something. The initial code is taken from one of the svelte tutorials https://svelte.dev/tutorial/svelte/deferred-transitions here don't think it's that good since it's passing state through 2 child (should probably use stores or something but I never got that far) and I am not sure if bind function synatax is mean to be used like this
Ganesh
Ganesh2mo ago
I just noticed that this is mutating state without bindable. Wonder why this doesn't generate warning
No description
Rägnar O'ock
Rägnar O'ockOP2mo ago
because you modify the value of a property of the object passed as a prop, not the object itself, so you don't break the reference link of the prop, so the reactivity system can still do it's thing
Rägnar O'ock
Rägnar O'ockOP2mo ago
that's... not what I had in mind...
Ganesh
Ganesh2mo ago
That feels like it's circumventing the reason bindable exists for
Rägnar O'ock
Rägnar O'ockOP2mo ago
honestly I don't understand why bindable exist...
Ganesh
Ganesh2mo ago
Also svelte will throw a warning if you directly bind like this bind:checked= {todos[0].done} It's only the each template that for some reason ignores it
Rägnar O'ock
Rägnar O'ockOP2mo ago
got any idea why it's doing what it's doing in the vid BTW ? 'cause I'm at a loss I suppose that's because of how I initialise the $state, but if that's not how you should do I don't know the correct way
Ganesh
Ganesh2mo ago
Are you using a key in your each directive I assume you're using each somewhere
Rägnar O'ock
Rägnar O'ockOP2mo ago
hu... IDK, here the bit of the template that does the each
<h2>Done :</h2>
{#each doneTodos as _,index}
<ul>
<TodoItem todo={doneTodos[index]}></TodoItem>
</ul>
{/each}
<h2>Done :</h2>
{#each doneTodos as _,index}
<ul>
<TodoItem todo={doneTodos[index]}></TodoItem>
</ul>
{/each}
Ganesh
Ganesh2mo ago
Index is different from key i think Index is just for your code use while key helps svelte track objects
Rägnar O'ock
Rägnar O'ockOP2mo ago
yeah index is just how far in the iteration we are so, I did have a key, I added one and now it's even more broken
Ganesh
Ganesh2mo ago
Lol Don't think I can speculate more from this. Maybe throw the whole code in svelte playground or something else and I'll mess around tomorrow. Too late now
Rägnar O'ock
Rägnar O'ockOP2mo ago
I used the kit template so I have no idea if it'll work in the playground i'll try tho
Ganesh
Ganesh2mo ago
👍
Rägnar O'ock
Rägnar O'ockOP2mo ago
https://svelte.dev/playground/b4d09314464d4fd5bee9eaac42346b1f?version=5.41.1 don't click anything without having a breakpoint for exceptions or your tab will crash
fucked up todo list • Playground • Svelte
Web development for the rest of us
Rägnar O'ock
Rägnar O'ockOP2mo ago
it's really not happy but if I try to do what the error message tels me to it gets worse... how in the fuck does this :
$effect(() => {
todo.doneOn = isChecked ? new Date() : null
})
$effect(() => {
todo.doneOn = isChecked ? new Date() : null
})
compile to this ?
$.user_effect(() => {
todo(todo().doneOn = $.get(isChecked) ? new Date() : null, true);
});
$.user_effect(() => {
todo(todo().doneOn = $.get(isChecked) ? new Date() : null, true);
});
so... from 5.9.0 onward you can do this :
<script>
const { todo }: {todo: Todo} = $props();

const isDone = () => todo.doneOn !== null;
const toggle = (isDoneNow: boolean) => todo.doneOn = isDoneNow ? new Date() : null;
</script>

<input bind:checked={isDone, toggle}>
<script>
const { todo }: {todo: Todo} = $props();

const isDone = () => todo.doneOn !== null;
const toggle = (isDoneNow: boolean) => todo.doneOn = isDoneNow ? new Date() : null;
</script>

<input bind:checked={isDone, toggle}>
which is more or less what I was looking for with a writable derived but I don't like the fact that the getter and the setter are independent thingies they don't make sense individually so why can't I have them as a single thing ?! so... now that what I had before works without complaining or crashing my tab how the fuck do I get logs from the +server.ts files? and how do I debug them too... 'cause the doc is not talking about that one bit, even thought it's like... kinda important nevermind... it's still complaining either I do this and it complain's about it not being reactive :
{#each pendingTodos as todo,index (todo.id)}
<ul>
<TodoItem bind:todo={pendingTodos[index]}></TodoItem>
</ul>
{/each}
{#each pendingTodos as todo,index (todo.id)}
<ul>
<TodoItem bind:todo={pendingTodos[index]}></TodoItem>
</ul>
{/each}
or I do this and the LSP complains about binding in each blocks something something...
{#each pendingTodos as todo,index (todo.id)}
<ul>
<TodoItem bind:todo={todo}></TodoItem>
^ Svelte: Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`)
</ul>
{/each}
{#each pendingTodos as todo,index (todo.id)}
<ul>
<TodoItem bind:todo={todo}></TodoItem>
^ Svelte: Cannot reassign or bind to each block argument in runes mode. Use the array and index variables instead (e.g. `array[i] = value` instead of `entry = value`, or `bind:value={array[i]}` instead of `bind:value={entry}`)
</ul>
{/each}
(╯°□°)╯︵ ┻━┻ so how the fuck am I supposed to do it then ? there's no way to send an update event like in vue, it doesn't like when i bind or when I don't bind what does it want me to dooooooo so... this works...
{#each pendingTodos as todo,index (todo.id)}
<ul>
<TodoItem bind:todo={() => todo, update => pendingTodos[index] = update}></TodoItem>
</ul>
{/each}
{#each pendingTodos as todo,index (todo.id)}
<ul>
<TodoItem bind:todo={() => todo, update => pendingTodos[index] = update}></TodoItem>
</ul>
{/each}
but I don't like it why do I need to have so much logic in the damn template to do anything ?! ok ... I got side tracked by zsh not killing the server when I kill a shell... and I don't know how to fix it either imma stop here before I break something
Ganesh
Ganesh2mo ago
I did a similar thing but it is complaining that I am binding a non reactive property
Ganesh
Ganesh2mo ago
No description
Ganesh
Ganesh2mo ago
dervied using deeply reactive proxy should be reactive tho I have no idea what is its problem
//App.svelte

<h2>Done :</h2>
{#each doneTodos as todo,index (todo.id)}
<ul>
<TodoItem todo={todo} bind:doneOn={todo.doneOn}></TodoItem>
</ul>
{/each}

<h2>To Do :</h2>
{#each pendingTodos as todo,index (todo.id)}
<ul>
<TodoItem todo={todo} bind:doneOn ={todo.doneOn}></TodoItem>
</ul>
{/each}
//App.svelte

<h2>Done :</h2>
{#each doneTodos as todo,index (todo.id)}
<ul>
<TodoItem todo={todo} bind:doneOn={todo.doneOn}></TodoItem>
</ul>
{/each}

<h2>To Do :</h2>
{#each pendingTodos as todo,index (todo.id)}
<ul>
<TodoItem todo={todo} bind:doneOn ={todo.doneOn}></TodoItem>
</ul>
{/each}
//Todo.svelte

let { todo, doneOn = $bindable() }: {todo: Todo, doneOn : null | Date} = $props();
function getChecked() { return doneOn !== null}
function setChecked(v) { doneOn = v ? new Date() : null}
//Todo.svelte

let { todo, doneOn = $bindable() }: {todo: Todo, doneOn : null | Date} = $props();
function getChecked() { return doneOn !== null}
function setChecked(v) { doneOn = v ? new Date() : null}
this doesn't get any warnings
dys 🐙
dys 🐙2mo ago
$derived variables aren't deeply reactive, meaning that if you reassign them (instead of mutating) the system won't track that & the connection will be broken.
Ganesh
Ganesh2mo ago
should I not mutate the derived values then?
dys 🐙
dys 🐙2mo ago
You can. My understanding is that the main issue is with reassignment.
Ganesh
Ganesh2mo ago
hmm so basically if instead of mutating todo like this todo.doneOn = new Date() if i reassign the whole todo todo = new Todo() it won't reflect any changes in the parent state right?
dys 🐙
dys 🐙2mo ago
I'm pretty sure that's correct.
Ganesh
Ganesh2mo ago
so that is why svelte is fine with me binding properties because it knows reassigning them will have changes but reassigning the object from derived array won't
dys 🐙
dys 🐙2mo ago
(Also, as a note, it would traditionally be doneAt.)
Ganesh
Ganesh2mo ago
I just copied from ragnar's code but my english is bad so i also didn't notice 😆
dys 🐙
dys 🐙2mo ago
doneOn makes some sense, but I've seen bunches of timestamp fields in database designs & they're uniformly <event>_at.
Ganesh
Ganesh2mo ago
gotcha I get confused since you use on with time but with days. "I am free on friday" while specfic time uses at? "I will be free at 12 pm" tho this is an awkward example
dys 🐙
dys 🐙2mo ago
That's the general rule I use. I'd say: “I was born on February 2ⁿᵈ at 11:37.”
Rägnar O'ock
Rägnar O'ockOP2mo ago
I initially wanted to just record the date, so on was ok... And then I used a timestamp instead xD ok... I'm lost, is this not how you use bind:this ? why is form null when onChange is called ?
Rägnar O'ock
Rägnar O'ockOP2mo ago
ah... found out why... the onchange is called after the form is dismounted and disposed of
Ganesh
Ganesh2mo ago
Can I ask why you're using @attach instead of just passing a submit handler to the form
Rägnar O'ock
Rägnar O'ockOP2mo ago
where? ah in the app it's because I wanted to prevent the submit event and you can't do that in a onsubmit because it's set as passive but now that I'm trying to have a backend DB I will need to actually submit the form
Ganesh
Ganesh2mo ago
The attach function runs in an effect iirc And you're not doing any cleanup So that might fuck something up
Rägnar O'ock
Rägnar O'ockOP2mo ago
I don't need any clean up I'm not dirtying anything and the element is never unmounted
Ganesh
Ganesh2mo ago
Won't it attach multiple event listeners if any values used in that function change Unless I'm misremembering
Rägnar O'ock
Rägnar O'ockOP2mo ago
it only reruns it when the element it's bound to changes from my understanding so if I unmounted the component or had a if block arround the form element
Ganesh
Ganesh2mo ago
Hmm I'll need to reread the docs Also I don't understand this. What do you mean by passive
Ganesh
Ganesh2mo ago
Ahh didn't know about this. Thanks
Rägnar O'ock
Rägnar O'ockOP2mo ago
that's... new
No description
ἔρως
ἔρως2mo ago
eh? how did you do that!?
Rägnar O'ock
Rägnar O'ockOP2mo ago
IDK
ἔρως
ἔρως2mo ago
204 - no content 500 - internal error
Rägnar O'ock
Rägnar O'ockOP2mo ago
yup
Rägnar O'ock
Rägnar O'ockOP2mo ago
alright... I got it to work
Rägnar O'ock
Rägnar O'ockOP2mo ago
my experience so far is... meh is there no way with use:enhance to keep the focus on the field that was focused before the submission but do all the rest of the logic it does ? I tried passing this as a parameter of the action, but to no avail
const onSubmit: SubmitFunction = () => ({update}) => {
update();
newTodoInput.focus();
};
const onSubmit: SubmitFunction = () => ({update}) => {
update();
newTodoInput.focus();
};
the focusing of body is still done after my call to focus
ἔρως
ἔρως2mo ago
if you're storing the date, why not store it as a timestamp, which is timezone independent? you're storing way more than what you need to
Rägnar O'ock
Rägnar O'ockOP2mo ago
because I'm just json stringifying the object as is
ἔρως
ἔρως2mo ago
you can use a replacer function
Rägnar O'ock
Rägnar O'ockOP2mo ago
I'm not doing ANY serialization work whatsoever xD
ἔρως
ἔρως2mo ago
oh it's automatic?
Rägnar O'ock
Rägnar O'ockOP2mo ago
hu... no this is my save function :
export async function writeEntity<tableName extends keyof DB, rowId extends keyofTable<tableName>>(table: tableName, id: rowId, value: DB[tableName][rowId]): Promise<DB[tableName][rowId]> {
await fs.writeFile(toEntityPath(table, id), JSON.stringify(value));
return value;
}
export async function writeEntity<tableName extends keyof DB, rowId extends keyofTable<tableName>>(table: tableName, id: rowId, value: DB[tableName][rowId]): Promise<DB[tableName][rowId]> {
await fs.writeFile(toEntityPath(table, id), JSON.stringify(value));
return value;
}
I'm just too lazy to add a serde layer XD I don't really want to go and make a shitty ORM (and yes there's more types than code in this function) I might need to make a serde layer tho... 'cause dates in json are just strings... which means my sorting is all broken
ἔρως
ἔρως2mo ago
why would it be broken? just sort alphabetically
Rägnar O'ock
Rägnar O'ockOP2mo ago
because I'm calling .valueOf on the dates and that fucks it up
Rägnar O'ock
Rägnar O'ockOP2mo ago
alright here's the repo, feel free to peruse and judge https://github.com/Ragnar-Oock/sveltekit-todos
GitHub
GitHub - Ragnar-Oock/sveltekit-todos: just a small todo list thingy...
just a small todo list thingy to try out svelte kit - Ragnar-Oock/sveltekit-todos
Rägnar O'ock
Rägnar O'ockOP2mo ago
I'll need to make an adapter for xoram, but I have no idea how that would work... and I'm too tired to try to figure it out
ἔρως
ἔρως2mo ago
let stupidForm: HTMLFormElement; 🤣
Rägnar O'ock
Rägnar O'ockOP2mo ago
yes xD I don't like the idea of having a bagilion forms on the page:/
ἔρως
ἔρως2mo ago
🤣 how do you know that that isn't the smart one?
Rägnar O'ock
Rägnar O'ockOP2mo ago
because i made sure it's dumb
ἔρως
ἔρως2mo ago
so, it should be dumbForm, not stupid 🤔
Rägnar O'ock
Rägnar O'ockOP2mo ago
same diff
ἔρως
ἔρως2mo ago
dumb is dumb, stupid is stupid 🤔 quick maths
dys 🐙
dys 🐙2mo ago
I often keep a form submission from going to the server with evt.preventDefault().
ἔρως
ἔρως2mo ago
from reading the code, don't think he has access to the raw event but i may be wrong
Rägnar O'ock
Rägnar O'ockOP2mo ago
you have, but id doesn't do anything because the listener is set as passive
ἔρως
ἔρως2mo ago
meh :/
dys 🐙
dys 🐙2mo ago
ISO GMT timestamps, YYYY/MM/ddTHH:mm:ssZ, will sort chronologically lexigraphically.
ἔρως
ἔρως2mo ago
and alphabetically
Rägnar O'ock
Rägnar O'ockOP2mo ago
yes, but that mean I need to do serde (or something else is broken but I don't care right now XD I'll check another day
ἔρως
ἔρως2mo ago
but 2025-10-21T22:10:10Z will always come after 2025-10-21T10:20:30Z sorted ascendingly however you sort it, it will always be correct so, something else is wrong
Rägnar O'ock
Rägnar O'ockOP2mo ago
yeah, but that doesn't work if one is a string and the other a date
ἔρως
ἔρως2mo ago
it does a.toString() > b.toString()
dys 🐙
dys 🐙2mo ago
new Date().toString()
"Tue Oct 21 2025 17:43:38 GMT-0400 (Eastern Daylight Time)"
new Date().toString()
"Tue Oct 21 2025 17:43:38 GMT-0400 (Eastern Daylight Time)"
ἔρως
ἔρως2mo ago
valueOf works too yeah, forgot that it gives an unusable format wait, no, it doesn't
Rägnar O'ock
Rägnar O'ockOP2mo ago
if it did I would not have issues...
ἔρως
ἔρως2mo ago
Date.parse works
Rägnar O'ock
Rägnar O'ockOP2mo ago
Date.valueOf gives a number while String.valueOf gives a string
ἔρως
ἔρως2mo ago
you can pass a date to it
Rägnar O'ock
Rägnar O'ockOP2mo ago
yes... but that means I need to do serde, and I was too lazy for that
ἔρως
ἔρως2mo ago
why serde for Date.parse? it's a built-in method and dates are comparable in js
Rägnar O'ock
Rägnar O'ockOP2mo ago
because I can't Date.parse my label or Id can I ? so I need to do each property individually
ἔρως
ἔρως2mo ago
:think:
Rägnar O'ock
Rägnar O'ockOP2mo ago
hense... serialization, deserialization serde
dys 🐙
dys 🐙2mo ago
Well, you can to a conditional on instanceof Date & call toISOString().
Rägnar O'ock
Rägnar O'ockOP2mo ago
I know how to do it XD I just didn't want to do it Hum... I want to play with service workers again... Might try to have one that proxies calls and send them as batches instead of one by one when multiple todo's are updated

Did you find this page helpful?