Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Events and handler functions

An event is a signal from the browser: the user clicked a button, typed text, submitted a form. In Svelte 5 an event handler is a regular element attribute named on + the event name: onclick, oninput, onsubmit. You pass a function to it.

A click handler

<!-- src/routes/+page.svelte -->
<script>
  let count = $state(0);
 
  function handleClick() {
    count++;
  }
</script>
 
<button onclick={handleClick}>Clicked: {count}</button>

Each click calls handleClick, and the number on the button grows. You pass the function itself — without parentheses. To pass an argument, use an arrow function: onclick={() => remove(todo.id)}.

The event object

The handler receives an event object — the same as in plain JavaScript. With a form it is used to cancel the page reload:

<!-- src/routes/+page.svelte -->
<script>
  let todos = $state([]);
 
  function addTodo(event) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    todos.push({ id: Date.now(), text: data.get('text'), done: false });
    event.currentTarget.reset();
  }
</script>
 
<form onsubmit={addTodo}>
  <input name="text" placeholder="New task" />
  <button>Add</button>
</form>
 
<p>Tasks: {todos.length}</p>

Type some text and press Enter or the button — the counter grows and the field is cleared. Modifiers like |preventDefault from Svelte 4 no longer exist: everything is done inside the function.

A function as a prop

A child component does not change data it does not own; it tells the parent about the event. For this the parent passes it a function as a regular prop. By convention the name of such a prop also starts with on:

<!-- src/lib/components/TodoItem.svelte -->
<script>
  let { todo, ontoggle, ondelete } = $props();
</script>
 
<li>
  <input type="checkbox" checked={todo.done} onchange={() => ontoggle(todo.id)} />
  {todo.text}
  <button onclick={() => ondelete(todo.id)}>Delete</button>
</li>

The component only calls the received functions and passes them the task's id.

Using it in the parent

<!-- src/routes/+page.svelte -->
<script>
  import TodoItem from '$lib/components/TodoItem.svelte';
 
  let todos = $state([
    { id: 1, text: 'Buy bread', done: false },
    { id: 2, text: 'Call Oleg', done: false }
  ]);
 
  function toggle(id) {
    const todo = todos.find((t) => t.id === id);
    todo.done = !todo.done;
  }
 
  function remove(id) {
    todos = todos.filter((t) => t.id !== id);
  }
</script>
 
<ul>
  {#each todos as todo (todo.id)}
    <TodoItem {todo} ontoggle={toggle} ondelete={remove} />
  {/each}
</ul>

Clicking a checkbox changes the task's status in the parent's array, the "Delete" button removes the row. Data is stored and changed in one place — +page.svelte.

Common mistake

Calling the function instead of passing it:

<!-- src/lib/components/TodoItem.svelte: wrong -->
<button onclick={remove(todo.id)}>Delete</button>

Here remove runs immediately when the markup is rendered and deletes the task, while onclick receives the result of the call. Wrap a call with arguments in an arrow function: onclick={() => remove(todo.id)}.

Thanks for reading!

Donate to the site — we work hard for you.