Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Shared state in .svelte.js files

While the to-do list lives in +page.svelte, other components get it only through props. When the same data is needed in several places — a counter in the header, the list on the page, a filter in a sidebar — the state is moved into a separate module. Runes work in files with the .svelte.js extension.

A module with state

Create the file src/lib/todos.svelte.js:

// src/lib/todos.svelte.js
export const todoStore = $state({
  items: [
    { id: 1, text: 'Buy bread', done: false },
    { id: 2, text: 'Call Oleg', done: false }
  ]
});
 
export function addTodo(text) {
  todoStore.items.push({ id: Date.now(), text, done: false });
}
 
export function toggleTodo(id) {
  const todo = todoStore.items.find((t) => t.id === id);
  todo.done = !todo.done;
}

The .svelte.js extension tells the compiler there are runes inside. The module exports a state object and the functions that change it.

Using it in components

Create a counter for the header:

<!-- src/lib/components/TodoCounter.svelte -->
<script>
  import { todoStore } from '$lib/todos.svelte.js';
 
  let remaining = $derived(todoStore.items.filter((t) => !t.done).length);
</script>
 
<span>Left: {remaining}</span>

And render it next to the list:

<!-- src/routes/+page.svelte -->
<script>
  import TodoCounter from '$lib/components/TodoCounter.svelte';
  import { todoStore, addTodo, toggleTodo } from '$lib/todos.svelte.js';
</script>
 
<h1>My tasks <TodoCounter /></h1>
 
<ul>
  {#each todoStore.items as todo (todo.id)}
    <li>
      <input type="checkbox" checked={todo.done} onchange={() => toggleTodo(todo.id)} />
      {todo.text}
    </li>
  {/each}
</ul>
<button onclick={() => addTodo('New task')}>Add</button>

Mark a task — the counter in the heading decreases at once, even though TodoCounter receives no props. Both components read the same object.

Why an object and not an array

An exported variable cannot be reassigned from another module: todoStore = ... in a component causes an error. That is why the state is wrapped in an object and its properties are changed: todoStore.items = [] works.

Stores

Before Svelte 5, shared state was built with stores from the svelte/store module: the writable function creates a store, and in a component its value is read with a $ prefix — $todos. Stores still work, and you will meet them in older code and libraries. For new code, $state in .svelte.js files is enough.

Careful with the server

In SvelteKit a module is loaded on the server once for all visitors. If you write a particular user's data into such state during server rendering, other visitors will see it. For a to-do list in the browser this is fine; user data on the server is passed through the load function and context.

Common mistake

Naming the file todos.js without .svelte. The Svelte compiler does not process it, and at runtime you get an error that $state is not defined. Always name files with runes *.svelte.js.

Thanks for reading!

Donate to the site — we work hard for you.