State management is an important aspect of programming that helps to control the flow and behaviour of data within an application. By effectively managing the state of an application, it becomes easier to maintain and scale the application, resulting in improved efficiency and performance.
State management also makes it easier for developers to share data between different components of an application and to organize code in a way that is more efficient and effective. This can help to improve the overall functionality of the application.
In this article, we’ll discuss the svelte store, which is one of the ways we can manage our states in svelte. It is also regarded as the most efficient way to manage complex data.
Prerequisites
To follow through with this article, the following prerequisites are required:
Familiarity with the svelte framework
Understanding svelte components: Svelte components are reusable, self-contained UI elements that can be easily shared, imported, and composed to build complex user interfaces.
Reactivity: State management in svelte is based on the concept of reactive programming. It means that all the variables used by the application are tracked and can be modified by the user. When a variable is changed, the application will update its state accordingly. This makes it easier to track changes and keep the application running smoothly, as svelte stores are reactive.
Getting started
Let’s first create a basic application using the CoderPad Svelte sandbox to show how we can pass data between components.
In the sandbox, create a new file Child.svelte
:
<!--- Child.svelte-->
<script>
export let greeting
</script>
<span>{greeting}</span>
Replace the content of App.svelte
with the code below:
<!--- App.svelte-->
<script>
import Child from "./Child.svelte"
</script>
<h1><Child greeting ="hello world"/></h1>
<h1><Child greeting ="hello john"/></h1>
<h1><Child greeting ="hello micheal"/></h1>
<h1><Child greeting ="hello doe"/></h1>
<h1><Child /></h1>
Here, we are defining the values of our variable greeting and passing them as props in our parent component, our App.svelte
, to the child component, Child.svelte
. Our child components then access these props and render them into the browser.
Component props are limited in how much data they can hold. Because of this, complex data sets would need to be broken down into multiple props, which can get messy and complicated.
We might also face a scenario whereby components do not need the props. We still have to send the props through them to be able to pass it further down in the component tree just for a particular component to access the data, which might not be a good thing if we are working with complex data in an application.
It would be nice to directly make the data available to the required component without manually drilling the props through every level of the component tree. One of the things we can use is the Context API. We then have the option of using a context.
The context API
The Svelte Context API is a feature that allows components to share states and functions with their children, grandchildren, and descendants. It provides a way of passing values and functions down the component tree without having to pass them at each level explicitly. This makes it easier to maintain and reason about the data flow in an application.
Svelte's context API is ideal for cross-component communication without complicating your codebase with props. The context API is made possible by two Svelte functions: getContext
and setContext
. Setting an object or value in the context and associating it with a key enables you to make it available anywhere within your app, as shown in the code below:
<script>
import { setContext } from "svelte";
const thisObject = {};
setContext("thisKey", thisObject);
</script>
We want thisKey to be available inside a different component within our app, so we import it using the getContext
function.
<script>
import { getContext } from "svelte";
const thisObject = getContext("thisKey");
</script>
For example, passing an array of todos between components:
<!-- App.svelte-->
<script>
import { setContext } from "svelte";
import Child from "./Child.svelte";
let todo = ["do chores", "clean", "code", "play football"];
setContext("key", todo);
</script>
<Child />
In the code above, we set the context key as key and pass todo object as the second argument.
We can now access the todo context from any other components like below:
<!-- TodoCard.svelte-->
<script>
import { getContext } from "svelte";
let todo = getContext("key");
</script>
<p>{todo}</p>
Although the Svelte context API is great for small applications, it becomes increasingly more work to manage the state simply and concisely as applications scale up.
Additionally, the context API does not provide any built-in methods for tracking changes to the data, making it difficult to track which pieces of data are outdated and need to be updated. For these reasons, it is better to use a store for managing and passing complex data between components.
Introducing svelte stores
A Svelte store is a JavaScript object that holds data similar to a variable. However, unlike a variable, Svelte stores can be observed, meaning that any part of the application can monitor changes to the store and respond accordingly.
It also makes it easier to keep track of changes to the data and makes it easier to modify the data when needed. The store is also the main way of sharing data between different parts of an application, making it easier to keep the code organized and maintainable.
The svelte store offers a solution that helps developers overcome these challenges by helping us manage our state. It detaches and manages it in an entirely different place so components can’t become bloated with unnecessary data. This makes Svelte Stores ideal for managing state in Svelte applications, as we can use them to store application data and allow components to reflect changes in the store.
Svelte has two primary types of stores, writable and readable stores. Svelte also offers a special type of store called a derived store.
Writable Stores
Writable stores are basically objects that hold values or attributes that various app components may access. They are the sources of information storage. Let's use the writable store to store a value we can later change or pass around in our application. We'll create our store as a JavaScript file, add some values and then export it:
<!-- weather.js -->
<script>
import writable from 'svelte/store'
export const weather = writable('sunny')
</script>
We can then import inside any other component where we'll need the data:
<script>
import { weather } from './weather.js'
</script>
We can alter the value in a writable store, and when we need to change the value of weather in any component where we imported it, we can use the unset()
method. The unset()
method is used to remove a value that has been previously set in a component's state:
<!-- Newweather.svelte -->
<script>
import { Weather } from './Weather.js'
city.set('Rainy')
</script>
We can also use the update()
method. This will help us invoke a callback in which the current value is passed as an argument:
<!-- Newweather.svelte -->
<script>
import { Weather } from './Weather.js'
const newWeather = 'Rainy'
function changeWeather() {
Weather.update(existing => newWeather)
}
</script>
Alternatively, we could have components use Svelte's subscribe()
function to monitor changes to the value we put in our store. The subscribe method in Svelte is used to add a reactive effect to a component:
<script>
import { Weather } from './Weather.js'
const watch = Weather.subscribe(value => {
console.log(value);
});
</script>
Readable stores
Readable stores, like writable stores, hold objects but cannot be modified by external components. You must set the value of a readable store when you create it. We use Readable stores to handle data that must be immutable. Below is an example of fetching the current date:
<!-- date.js -->
import { readable } from 'svelte/store'
export const date = readable(new Date())
<!-- app.svelte -->
<script>
import {date} from "./date.js"
</script>
<h1>Today's date is{$date}</h1>
Derived Stores
Derived stores, as the name implies, are derived from another store. The value of a derived store is updated when the store from which it was derived is updated. Derived stores are simply readable stores, but their values come from somewhere else, at least partially:
import { derived } from "svelte/store";
import { date } from "./store.js";
export const test = derived(date, ($date) => {
return (
$counter.getHours() +
":" +
$counter.getMinutes() +
":" +
$counter.getSeconds()
);
});
Our derived store can then be imported into another component as follows:
<!-- App.svelte -->
<script>
import { test } from "./derived.js";
</script>
<h1>
{$test}
</h1>
Svelte store example: Building a student data list
To showcase how Svelte store could be used in real-time, we will be building an app where we’ll be able to manage state in svelte.
We can set up a new project folder and install svelte for this project example by following the steps mentioned earlier, or we can use the one created previously.
Project scope
For this project, we’ll be building a student data list that will display some student data, and we’ll do that with the help of a store.
Creating a store
We’ll be creating a store that will contain some student data. Inside the src folder, create a folder called stores
and create a file called students.js
. This file will define the methods for updating our store's values and resetting them to their default values when needed. Our students.js
will contain the following:
import { writable } from "svelte/store";
const STUDENTS = [
{ name: "john", surname: "doe", age: 17 },
{ name: "micheal", surname: "angelo", age: 21 },
];
const { subscribe, set, update } = writable(STUDENTS);
const addStudent = (student) =>
update((students) => {
return [...students, student];
});
const reset = () => {
set(STUDENTS);
};
export default {
subscribe,
addStudent,
reset,
};
Now we’ll import the exported methods, which are the subscribe
, addStudent
, and reset
, into the component we’ll create shortly and use them to subscribe, add new values and reset our student list.
Creating our components
Next, we will create components that accept the methods exported from our store. We'll be creating two components: StudentPage.svelte
, which will be used to add new student data, and StudentList
, which will display the list of students that have been added.
In StudentPage.svelte
, add the following:
<script>
import students from "./stores/students";
</script>
<div class="studentPage">
{#each $students as student}
<p>{student.name} {student.surname}</p>
<p>{student.age}</p>
<hr />
{/each}
</div>
We’ll make our StudentList
a form so we can add new student data as well as reset them:
<script>
import students from "./stores/students";
// binding values
let name = "";
let surname = "";
let age = "";
// submit student
const submitStudent = () => {
students.addStudent({ name, surname, age });
// reset values
name = surname = age = "";
};
</script>
<div class="studentList">
<input type="text" bind:value={name} placeholder="Enter student make" />
<input type="text" bind:value={surname} placeholder="Enter student surname" />
<input type="year" bind:value={age} placeholder="Enter student age" />
<input type="submit" on:click={submitStudent} value="Include Student" />
<input type="submit" on:click={students.reset} value="Reset Student list" />
</div>
Let’s add some basic styling to our components. In our app.css
, paste this in:
.studentList {
margin: 0 auto;
box-shadow: 0 0 30px 0;
height: auto;
max-width: 410px;
background-color: rgb(231, 231, 231);
padding: 20px;
}
input {
margin: 30px 0 0;
height: 30px;
width: 100%;
padding: 0 10px;
outline: none;
border: none;
background-color: rgb(231, 231, 231);
border-bottom: 1px solid #000;
font-size: 1.1rem;
transition: .3s;
}
button {
border-radius: 6px;
margin: 20px 0 0;
height: 40px;
width: 100%;
color: #fff;
padding: 0;
outline: none;
cursor: pointer;
transition: .3s;
border: none;
}
.button {
background-color: rgba(26, 26, 204, 0.582);
}
.button:hover {
background-color: rgb(26, 26, 204);
}
.reset {
background-color: rgb(189, 42, 42);
}
.reset:hover {
background-color: rgb(255, 0, 0);
}
/* studentpage styling */
.studentPage {
padding-top: 30px;
display: flex;
justify-content: center;
align-items: center;
}
.table-bordered {
border-width: 1px 1px 1px 0px;
border-style: solid solid solid none;
border-color: #fff #fff #fff -moz-use-text-color;
border-collapse: collapse;
border-radius: 4px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
max-width: 100%;
overflow-x: auto;
}
.table-bordered th,
.table-bordered td {
min-width: 150px;
padding: 8px;
line-height: 20px;
text-align: left;
vertical-align: top;
border: 1px solid #fff;
}
.table-bordered th {
background-color: rgb(88, 83, 83);
color: #fff;
}
We’ll then import into each of our components using:
import "./app.css";
The last thing we’ll do is import both our components into our app.svelte
:
<script>
import StudentList from "./StudentList.svelte";
import StudentPage from "./StudentPage.svelte";
</script>
<div>
<StudentList />
<StudentPage />
</div>
We just created an app and managed it using svelte stores! Play with the application in this sandbox.
Conclusion
Svelte stores are an effective way to manage and access data in your Svelte applications. They provide a centralized way to store data, making it easier to keep track of changes and make modifications when needed. Using a Svelte store is straightforward and can make your code cleaner and more maintainable.
Top comments (0)