Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: state management #8547

Merged
merged 26 commits into from
Feb 28, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4353400
docs: state management
dummdidumm Jan 16, 2023
b198f59
fix
dummdidumm Jan 16, 2023
c1ffd0b
fix the fix
dummdidumm Jan 16, 2023
d66695b
Merge branch 'master' into state-management
dummdidumm Jan 24, 2023
dba26f3
update
dummdidumm Jan 24, 2023
7201509
fix link
dummdidumm Jan 24, 2023
53285d9
Update documentation/docs/20-core-concepts/60-state-management.md
dummdidumm Jan 25, 2023
978ac41
Update packages/kit/src/runtime/app/stores.js
dummdidumm Jan 27, 2023
f14481c
Merge branch 'master' into state-management
dummdidumm Feb 8, 2023
fed53b2
move details to form actions, add url section
dummdidumm Feb 8, 2023
044e3cb
shorten, do not use form in example
dummdidumm Feb 8, 2023
98c9b13
remove now obsolete paragraph, add info on when params and url can ch…
dummdidumm Feb 8, 2023
bd87958
links
dummdidumm Feb 8, 2023
aecbc2b
changeset, link fix
dummdidumm Feb 8, 2023
a18f31f
info on when which load function runs
dummdidumm Feb 8, 2023
1d123cb
fix
dummdidumm Feb 8, 2023
2e69c32
closes #8302
dummdidumm Feb 8, 2023
7f8f69e
Merge branch 'master' into state-management
dummdidumm Feb 9, 2023
e908196
Update documentation/docs/20-core-concepts/50-state-management.md
dummdidumm Feb 9, 2023
99a8907
details, example
dummdidumm Feb 9, 2023
44773f5
Merge branch 'state-management' of https://github.com/sveltejs/kit in…
dummdidumm Feb 9, 2023
29a8a4c
Update documentation/docs/20-core-concepts/20-load.md
dummdidumm Feb 16, 2023
8798504
merge master
Rich-Harris Feb 28, 2023
42b8db8
Update documentation/docs/20-core-concepts/20-load.md
Rich-Harris Feb 28, 2023
b7e90e3
Update documentation/docs/20-core-concepts/50-state-management.md
Rich-Harris Feb 28, 2023
87c06c5
docs: state management part 2 (#9239)
Rich-Harris Feb 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions documentation/docs/20-core-concepts/60-state-management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
dummdidumm marked this conversation as resolved.
Show resolved Hide resolved
title: State Management
---

Managing state is one of the hardest parts in application development. In this section, we will go over the different types of state, how SvelteKit can help you with managing it, and different patterns for various use cases.

## Types of state

Apps have a multitude of different states. State can broadly be classified by two adjacent categories: The semantic category of the state, and how it is represented.

Semantic categories of state are:
- Local state: State that is only relevant inside one component, and managed inside it.
- Global state: State that is relevant to many or all parts of the application, and is likely modified from different places.

How state can be represented:
- JavaScript variable: Not visible from the outside
- URL: What page you're on, but also information for the page itself through query parameters for example
- Cookies: Mostly modifiable through the server, contains for example information about the current user. Can be used to persist state across browser tabs or reloads.
dummdidumm marked this conversation as resolved.
Show resolved Hide resolved
- localStorage/sessionStorage: Persist state across tabs or reloads, for example user settings.
- API/Backend: Data that is saved inside a database, for example the list of TODOs the user entered.

The above are not exhaustive lists, and different measures could be used to categorize state. The key point is to think about state systematically before adding it to your application.

## Careful with global state
dummdidumm marked this conversation as resolved.
Show resolved Hide resolved

If you are creating an [SPA](/docs/glossary#csr-and-spa) with SvelteKit, you can create global state freely, as you can be sure that it's only initialized inside the user's browser. If you use [SSR](/docs/glossary#ssr) however, you have to watch out for a couple of things when managing state. In many server environments, a single instance of your app will serve multiple users (this is not specific to SvelteKit - it's one of the gotchas of working with such environments). For that reason, per-request or per-user state must not be stored in global variables.

Consider the following example where the user is set from inside a `load` function:

```js
/// file: +page.js
// @filename: ambient.d.ts
declare module '$lib/user' {
export const user: { set: (value: any) => void };
}

// @filename: index.js
// ---cut---
// DON'T DO THIS!
import { user } from '$lib/user';

/** @type {import('./$types').PageLoad} */
export async function load({ fetch }) {
const response = await fetch('/api/user');
user.set(await response.json());
}
```

If you are using SSR, the `load` function will run on the server initially, which means that the whole server instance which serves _all_ requests from _all_ users has its `user` state set to the one just requested from the API. To scope this to a single user, you have a couple of options:

- if you need to access the user state only inside `load` functions, use `locals` in server `load` functions
- if you need to persist the user state across reloads, but only need to access it inside `load` functions, use `cookies` in server `load` functions
dummdidumm marked this conversation as resolved.
Show resolved Hide resolved
- if you need to access and update the state inside components, use Svelte's [context feature](https://svelte.dev/docs#run-time-svelte-setcontext). That way, the state is scoped to components, which means they are not shared across different requests on the server. The drawback is that you can only set and subscribe to the store at component initialization. SvelteKit's stores from `$app/stores` for example are setup like this (which is why you may have encountered a related error message)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be useful to have an example of this here ?
I am not sure myself what is meant with this, but are you referring to returning data from load, placing it in a store and then use setContext in the page to make it available ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes this would be the way - and I agree this probably needs a code example

22 changes: 19 additions & 3 deletions packages/kit/src/runtime/app/stores.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,23 @@ export const getStores = () => {
export const page = {
/** @param {(value: any) => void} fn */
subscribe(fn) {
const store = getStores().page;
const store = __SVELTEKIT_DEV__ ? get_store('page') : getStores().page;
return store.subscribe(fn);
}
};

/** @type {typeof import('$app/stores').navigating} */
export const navigating = {
subscribe(fn) {
const store = getStores().navigating;
const store = __SVELTEKIT_DEV__ ? get_store('navigating') : getStores().navigating;
return store.subscribe(fn);
}
};

/** @type {typeof import('$app/stores').updated} */
export const updated = {
subscribe(fn) {
const store = getStores().updated;
const store = __SVELTEKIT_DEV__ ? get_store('updated') : getStores().updated;

if (browser) {
updated.check = store.check;
Expand All @@ -55,3 +55,19 @@ export const updated = {
);
}
};

/**
* @template {keyof ReturnType<typeof getStores>} Name
* @param {Name} name
* @returns {ReturnType<typeof getStores>[Name]}
*/
function get_store(name) {
try {
return getStores()[name];
} catch (e) {
throw new Error(
`Store '${name}' is not available outside of a Svelte component on the server, as its bound to component context there. ` +
dummdidumm marked this conversation as resolved.
Show resolved Hide resolved
'For more information, see https://kit.svelte.dev/docs/state-management#careful-with-global-state'
dummdidumm marked this conversation as resolved.
Show resolved Hide resolved
);
}
}
8 changes: 7 additions & 1 deletion packages/kit/types/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,16 +287,22 @@ declare module '$app/stores' {

/**
* A readable store whose value contains page data.
*
* On the server, this store can only be subscribed to during component initialization. On the client, it can be subscribed to at any time.
*/
export const page: Readable<Page>;
/**
* A readable store.
* When navigating starts, its value is a `Navigation` object with `from`, `to`, `type` and (if `type === 'popstate'`) `delta` properties.
* When navigating finishes, its value reverts to `null`.
*
* On the server, this store can only be subscribed to during component initialization. On the client, it can be subscribed to at any time.
*/
export const navigating: Readable<Navigation | null>;
/**
* A readable store whose initial value is `false`. If [`version.pollInterval`](https://kit.svelte.dev/docs/configuration#version) is a non-zero value, SvelteKit will poll for new versions of the app and update the store value to `true` when it detects one. `updated.check()` will force an immediate check, regardless of polling.
* A readable store whose initial value is `false`. If [`version.pollInterval`](https://kit.svelte.dev/docs/configuration#version) is a non-zero value, SvelteKit will poll for new versions of the app and update the store value to `true` when it detects one. `updated.check()` will force an immediate check, regardless of polling.
*
* On the server, this store can only be subscribed to during component initialization. On the client, it can be subscribed to at any time.
*/
export const updated: Readable<boolean> & { check(): Promise<boolean> };

Expand Down