Sharing State Between Components
Learning Objectives
- You know how to separate state from components.
- You know how to share state between components.
Components and state
So far, our components have used the state that is declared in the component itself. This means that each instance of the component has its own copy of the variables — i.e., the state —, and the state is not shared between components.
For example, if the following component would be added to a page twice, both versions of it would have their own count variable, and their counts could be incremented independently.
<script>
let count = $state(0);
</script>
<p>Count is {count}</p>
<button onclick={() => count++}>Increment</button>
When multiple components need access to the same state, the state needs to be shared.
Pattern for separating state from components
Svelte has a pattern for sharing state between components. The pattern is to create a state object that can be imported by the components that need access to the state. This way, the state is not tied to a specific component, and the state can be shared between multiple components.
To avoid directly mutating the state from multiple components, the state object typically provides functions for updating the state. This way, the state can be updated in a controlled manner, and the components that use the state do not need to know about the internal structure of the state.
Let’s start with a simple shared object for sharing a count. Create a folder called states to the lib folder within the src folder of the project. Inside the states folder, create a file called countState.svelte.js and add the following code to it:
let count = $state(0);
const useCountState = () => {
return {
get count() {
return count;
},
increment: () => count++,
};
};
export { useCountState };
The above outlines a function called useCountState that returns an object with two properties: count and increment. The count property is a getter that returns the value of the count variable. The increment property is a function that increments the count variable.
Now, you could modify the earlier component to use the shared state as follows:
<script>
import { useCountState } from "$lib/states/countState.svelte.js";
const countState = useCountState();
</script>
<p>Count is {countState.count}</p>
<button onclick={() => countState.increment()}>Increment</button>
Further, you could create another component that uses the shared state. The following example shows a component that displays the count multiplied by two.
<script>
import { useCountState } from "$lib/states/countState.svelte.js";
const countState = useCountState();
</script>
<p>Count multiplied by two is {countState.count * 2}</p>
Now, if the two components are imported to the same page, they will share the same state. Whenever the count is changed in the first component, the second component will also update to reflect the new count.
The above pattern is a module-level singleton. The state variable count is defined at the module level, meaning that it is shared across all imports of the module. The useCountState function returns an object that provides access to the shared state and functions for updating it.
Furthermore, adding the suffix .svelte.js to the file name indicates that the file is a Svelte module, allowing the use of Svelte-specific features such as the $state directive.
In a similar fashion, we could e.g. have a shared state that contains text. Now, instead of a count, the state contains a text variable, and a function for updating the text variable. The following shows how the shared state could look like.
let text = $state("Hello, World!");
const useTextState = () => {
return {
get text() {
return text;
},
updateText: => (newText) {
text = newText;
},
};
};
export { useTextState };
Now, we could have a component that displays the text and say its length, and another component that allows updating the text. The following shows how the two components could look like.
First, a component for displaying the text and its length.
<script>
import { useTextState } from "$lib/states/textState.svelte.js";
const textState = useTextState();
</script>
<p>Text is: {textState.text}</p>
<p>Text length is: {textState.text.length}</p>
And then, a component for updating the text.
<script>
import { useTextState } from "$lib/states/textState.svelte.js";
const textState = useTextState();
let newText = $state("");
</script>
<input type="text" bind:value={newText} />
<button onclick={() => textState.updateText(newText)}>Update Text</button>
Now, if both components are added to the same page, they will share the same text state. Whenever the text is updated, shown information about the text will be updated to reflect the new text.
List as a shared state
While the previous examples showcased sharing primitive data types, shared state works also with lists. Let’s take our earlier book example, and modify it so that the list of books is stored in a shared state.
Book state
Create a file called bookState.svelte.js to the folder states within the lib folder of the project, creating the folder states if it does not yet exist, and place the below code to the file.
let bookState = $state([
{ id: 1, name: "HTML for Hamsters" },
{ id: 2, name: "CSS: Cannot Style Sandwiches" },
{ id: 3, name: "JavaScript and the Fifty Shades of Errors" },
]);
const useBookState = () => {
return {
get books() {
return bookState;
},
};
};
export { useBookState };
Note the pattern in the code used for separating state. The state object is created as a function that returns an object with properties for accessing and updating the state. Importantly, the concrete state is accessed through a getter function. When a component uses the state object, it imports the state object by calling the function that creates the state object.
BookList with book state
Then, modify BookList.svelte to use the useBookState function to retrieve the books and display them. The modified file would look as follows.
<script>
import { useBookState } from "$lib/states/bookState.svelte.js";
let bookState = useBookState();
</script>
<ul>
{#each bookState.books as book}
<li>
<a href={`/books/${book.id}`}>{book.name}</a>
</li>
{/each}
</ul>
Now, the page at the path /books should still work as before, but the list of books is now retrieved from the shared state.
Book and using book state
Next, we can modify the Book.svelte component to use the useBookState function.
Let’s first add a method called getOne to the useBookState function in the bookState.svelte.js file, allowing retrieving individual books by ID. The modified useBookState with the getOne function would look as follows.
let bookState = $state([
{ id: 1, name: "HTML for Hamsters" },
{ id: 2, name: "CSS: Cannot Style Sandwiches" },
{ id: 3, name: "JavaScript and the Fifty Shades of Errors" },
]);
const useBookState = () => {
return {
get books() {
return bookState;
},
// new function
getOne: (id) => {
return bookState.find((b) => b.id === id);
},
// new function end
};
};
export { useBookState };
Next, adjust the Book.svelte to use the getOne function to retrieve the book by its ID; now, we get to show the name of the book as well.
<script>
let { bookId } = $props();
import ChapterList from "$lib/components/books/ChapterList.svelte";
import { useBookState } from "$lib/states/bookState.svelte.js";
let bookState = useBookState();
let book = bookState.getOne(bookId);
</script>
<h1>{book.name}</h1>
<ChapterList {bookId} />
Fixing a bug
Now, when we try to access the page of an existing book, there’s an error. The key issue here is that the getOne function looks for a book with bookState.find((b) => b.id === id) using === for comparison. The route parameter bookId is a string, while the id in the book object is a number. This means that the comparison will always return false, and the book variable will be undefined.
When the book is undefined, trying to access book.name results in an error because we are trying to access a property of undefined.
Let’s fix this by converting the id to a number before passing it to a book. Modify the +page.svelte in the src/routes/books/[bookId] folder to the following.
<script>
import Book from "$lib/components/books/Book.svelte";
let { data } = $props();
let bookId = parseInt(data.bookId);
</script>
<Book bookId={bookId} />
Now, when we access the path /books/1, we see the name for the book with the identifier 1. Similarly, when we access the path /books/2, we see the book with the identifier 2.
Adding a book
Finally, let’s add functionality for adding a book. First, modify the bookState.svelte.js to add a function for adding a book. The function takes the name of the book as a parameter, and creates a new book object with an identifier and the name, and adding the book object to the list of books.
let bookState = $state([
{ id: 1, name: "HTML for Hamsters" },
{ id: 2, name: "CSS: Cannot Style Sandwiches" },
{ id: 3, name: "JavaScript and the Fifty Shades of Errors" },
]);
const useBookState = () => {
return {
get books() {
return bookState;
},
getOne: (id) => {
return bookState.find((b) => b.id === id);
},
// new function
addBook: (name) => {
bookState.push({ id: bookState.length + 1, name });
},
// new function end
};
};
export { useBookState };
Next, create a component called AddBook.svelte to the src/lib/components/books folder, and place the following content to the file.
<script>
import { useBookState } from "$lib/states/bookState.svelte.js";
let bookState = useBookState();
let bookName = $state("");
const addBook = () => {
bookState.addBook(bookName);
bookName = "";
};
</script>
<input type="text" bind:value={bookName} placeholder="Book name" />
<button onclick={addBook}>Add Book</button>
Then, finally, import the AddBook component to the src/routes/books/+page.svelte file, and add it below the heading. The modified file would look as follows.
<script>
import BookList from "$lib/components/books/BookList.svelte";
import AddBook from "$lib/components/books/AddBook.svelte";
</script>
<h1>Books</h1>
<AddBook />
<BookList />
Now, when the user navigates to the path /books, they can add a new book by entering the name of the book and clicking the “Add Book” button. The new book will be added to the list of books, and the list will update to show the new book.
This is shown in Figure 1 below, where an additional book has been added to the list of books.

/books provides functionality for adding new books to the list.Object as a shared state
The shared state could also be an object. Let’s work through this by introducing a shared state for book chapters.
Book chapter state
Create a file called chapterState.svelte.js to the folder states within the lib folder of the project, and place the below code to the file. Note that now, the chapters are no longer simply numbers, but they also have identifiers and names.
let chapterState = $state({
1: [
{ id: 1, name: "Hamster Homes" },
{ id: 2, name: "Tiny Tables" },
{ id: 3, name: "Forms & Seeds" },
],
2: [
{ id: 1, name: "Styling Bread" },
{ id: 2, name: "Decorating Lettuce" },
{ id: 3, name: "Advanced Pickles" },
{ id: 4, name: "Garnish Mastery" },
],
3: [
{ id: 1, name: "Oops 101" },
{ id: 2, name: "Many Errors" },
{ id: 3, name: "Fifty More Bugs" },
],
});
const useChapterState = () => {
return {
get chapters() {
return chapterState;
},
};
};
export { useChapterState };
ChapterList with chapter state
Then, modify ChapterList.svelte to use the useChapterState function to retrieve the chapters for a book and display them. The modified file would look as follows.
<script>
let { bookId } = $props();
import { useChapterState } from "$lib/states/chapterState.svelte.js";
let chapterState = useChapterState();
</script>
<ul>
{#each chapterState.chapters[bookId] as chapter}
<li>
<a href={`/books/${bookId}/chapters/${chapter.id}`}>{chapter.name}</a>
</li>
{/each}
</ul>
Now, when we access the path /books/1, we see the book with ID 1, and a list of chapters for that book. Similarly, when we access /books/2, we see the book with ID 2, and so on. The chapter list component also no longer directly contains the list of chapters, but instead retrieves the chapters from the shared state.
The application should look like the one shown in Figure 2.

/books/1 shows the book name and provides the chapter names as links.Chapter
Finally, let’s create a separate component for displaying a chapter. Create a file called Chapter.svelte to the src/lib/components/books folder, and place the following content to the file.
<script>
let { bookId, chapterId } = $props();
import { useBookState } from "$lib/states/bookState.svelte.js";
import { useChapterState } from "$lib/states/chapterState.svelte.js";
let bookState = useBookState();
let chapterState = useChapterState();
let book = bookState.getOne(bookId);
let chapter = chapterState.chapters[bookId].find((c) => c.id === chapterId);
</script>
<h1>{book.name}</h1>
<h2>{chapter.name}</h2>
<p>This is chapter {chapter.id} of book {book.id}.</p>
Now, the individual chapter pages can use the Chapter component to display the chapter information. Modify the +page.svelte file in the src/routes/books/[bookId]/chapters/[chapterId] folder to the following.
<script>
import Chapter from "$lib/components/books/Chapter.svelte";
let { data } = $props();
let bookId = parseInt(data.bookId);
let chapterId = parseInt(data.chapterId);
</script>
<Chapter bookId={bookId} chapterId={chapterId} />
Now, navigating to an individual chapter page shows both the name of the book and the name of the chapter, with accompanying information about the indentifiers. This is shown in Figure 3 below.

/books/3/chapters/1 shows the book name, the chapter name, and their identifiers.Summary
In summary:
- A common pattern for separating state is to create a state object that can be imported by the components that need access to the state.
- The state object provides a getter for the data and functions for updating the state, so that the state can be updated in a controlled manner.
- The shared state can be of any data type, including primitive types, arrays, and objects.