[svelte store][02] svelte store's writable()
About Svelte Store
let's summarize the writable
, subscribe
, and unsubscribe
provided by the svelte store.
1. Before using svelte store
svlete provides writable()
function for state management.
TSimport { writable } from 'svelte/store';
we'll start with a simple example that fetches weather information from the server and renders it to the screen.
the following weather information is provided by the server
JS{
temperature: "35C",
humidity:"65%"
time: new Date()
}
mimic the function to get the server data as follows (api.js
)
api.jsexport const api = {
weather:{
load(lat, lng) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
temperature: "20C",
humidity: "65%",
time: new Date()
})
}, 2000)
})
}
}
}
- we're mimicking a delay of about 2 seconds.
the Weather.svelte
component is implemented simply as follows.
Weather.svelte#01<script
import {writable} from 'svelte/store'
import {api} from './api'
const weather = writable(undefined);
api.weather.load().then(data => {
$weather = data;
})
</script>
Weather.svelte#01<div> <h5>weather</h5
<h5>weather</h5>
{#if $weather}
<span>{$weather.temperature}</span>
<span>{$weather.humidity}</span>
{:else}
<span>...loading</span>
{/if}
</div>
2. What is "$weather"
the object returned by calling the function writable
on Weather.svelte
is called a store object
.
the store object provides reactive functionality that automatically updates the screen when the data changes.
A store is an object that allows reactive access to a value via a simple store contract. The svelte/store module contains minimal store implementations which fulfill this contract.
- prefix stores with $ to access their values
- user-created objects are also recognized as store objects, even if they are not
writable
functions provided by svelte, as long as they follow a few rules.
these store objects can be read or written to by simply prefixing them with a dollar sign ($
) only within **_*.svelte files.
In this document, we will refer to the dollar sign as the
store prefix
.
if we implemented the following code to update data without the store prefix, it would look like this
without store prefix// using store prefix
api.weather.load().then((data) => {
$weather = data;
});
// without store prefix
api.weather.load().then((data) => {
weather.set(data);
});
- store.set(...) - For
$weather = data
to work, the store object must implement the methodset
. - in the view area (HTML), the area referenced by
$weather
will be updated.
more code is needed to implement the Weather.svelte#01
code to detect state changes without the store prefix.
Weather.svelte#01<script
import {writable} from 'svelte/store'
import {api} from './api'
const weather = writable(undefined);
api.weather.load().then(data => {
$weather = data;
})
</script> </script
<div>
{#if $weather}
<span>{$weather.temperature}</span>
<span>{$weather.humidity}</span>
{:else}
<span>...loading</span>
{/if}
</div>
Weather.svelte#02
is the implementation when store prefix is not used.
Weather.svelte#02<script
import {writable} from 'svelte/store'
import {api} from './api'
const weather = writable(undefined);
api.weather.load().then(data => {
weather.set(data);
})
let _wdata = undefined;
weather.subscribe((weatherData) => {
_wdata = weatherData
})
</script>
Weather.svelte#02<div>
{#if _wdata}
<span>{_wdata.temperature}</span>
<span>{_wdata.humidity}</span>
{:else}
<span>...loading</span>
{/if}
</div>
- subscribe(callback) - The store object must implement the method
subscribe
to be reactively updated in the view.
**If you don't use the *store prefix, you must register a callback function with the subscribe
method directly, such as Weather.svelte#02
, to receive state changes.
you'll also need a variable (_wdata
) to store the state in.
SVELTE let _wdata; // required to get state of store object
weather.subscribe((weatherData) => {
_wdata = weatherData
})
- in svelte, assigning a value to a variable as above works reactively.
_wdata = weatherData
link - however, it doesn't work reactively if the property inside the object changes. if I change it to
_wdata.temperature = "40C"
, the screen doesn't update.
There is a bug in the code of Weather.svelte#02.
3. subscribe, unsubscribe
let's say the weather information is a global state shared by multiple components.
Weather.svelte import {writable} from 'svelte/store'
import {api} from './api'
// Don't create global state inside the component.
const weather = writable(undefined);
// Don't call the API directly
api.weather.load().then(data => {
$weather = data;
})
- for illustration purposes, we created the store object inside the component.
in applications, it's common for multiple components to share a single state.
create a file to manage the global state as follows
weather.store.jsimport { writable } from 'svelte/store'
import { api } from './api'
const weather = writable(undefined);
export default {
weather,
loadWeather: async () => {
const data = await api.weather.load()
weather.set(data) // makes view to be updated
}
}
the weather.store.js
that manages the state looks like this
- state management is decoupled from components.
weather.svelte#03` imports the global state as shown below.
Weather.svelte#03 import {writable} from 'svelte/store'
import {weather, loadWeather} from './weather.store'
let _wdata;
const unsub = weather.subscribe((weatherData) => {
_wdata = weatherData
})
// Pull the API, weather, out of the component.
loadWeather()
let's say it's used inside another component (Slide.svelte), as shown below.
SlideMenu.svelte
<menu>
<Weather/>
</menu>
- The slide menu appears on the screen only when you press the button (toggling).
A <Weahter/>
component is created each time the Slide menu appears on the screen.
the callback function is registered as weather.subscribe
in Weather.svelte#03 is called.
and the callback function registered in the state weather
is not removed when the slide menu is closed.
what does this mean?
Slide menu is expanded every time, a callback is registered.
- if you opened the menu 4 times, you have 4 callbacks registered on the store object
weather
. - if the state changes once, 4 callbacks are called.
- the screen update happens 4 times.
to avoid the bug, Weather.svelte#03
should be supplemented as follows.
Weather.svelte#04 import {onDestroy} from 'svelte';
let _wdata;
const unsub = weather.subscribe((weatherData) => {
_wdata = weatherData
})
onDestory(() => {
unsub(); // removes callback internally
}
- the
subscribe
function of the store object must return another function (unsub
) as its return value. - this function must implement the ability to release any callbacks registered when it is called (
unsub
). - components typically call this function on
onDestory
.
because it is impossible to adhere to these conventions perfectly, svelte provides a store prefix syntax.
the store prefix syntax in svelte automatically makes subscription, unsubscription work.
4. Svelte store prefix '$'
Weather.svelte#05 - script import {writable} from 'svelte/store'
import {weather, loadWeather} from './weather.store'
// let _wdata;
// const unsub = weather.subscribe((weatherData) => {
// _wdata = weatherData
// })
loadWeather()
- remove the code to subscribe from the <script>.
- remove the temporary variable as well.
in the HTML area, access the store object by prefixing it with store prefix.
Weather.svelte#05 - html<div> <h5>weather</h5
<h5>weather</h5>
{#if $weather}
<span>{$weather.temperature}</span>
<span>{$weather.humidity}</span>
{:else}
<span>...loading</span>
{/if}
</div>
if we look at weather.store.js
again, we have code that updates the store object.
weather.store.js loadWeather: async () => {
const data = await api.weather.load();
weather.set(data); // makes view to be updated
}
- the store prefix cannot be used because
weather.store.js
is a regular js file, not a svelte file.
changing its state with store.set(...)
will update the component Weather.svelte
.
Weather.svelte {#if $weather}
...
{/if}
Conclusion: Whenever possible, use the
store prefix
when using store objects in components.
1.4. Using STORE objects in plain JS(TS) files
as already mentioned above, the STORE PREIFX syntax is only recognized within SVELTE files. in JS(TS) files, it is treated as a normal variable.
as your application grows, you may find that your STORE objects depend on each other, in which case you'll want to manage subscriptions manually.