Improve `page.state` type
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 20.8k
- Forks
- 2.3k
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 156
Description
Describe the problem
Currently, page.state is typed as App.PageState, which is a global type.
This does not seem the best way to type an object which may be used for shallow navigation/history snapshotting in different places, each with different requirements.
For example, in a multi-step form I may want to persist the form state in page.state via pushState so that the user can navigate back and edit previous inputs.
On another page, I may want to control which widgets are open.
Both of these scenarios may happen in the same app and a global type is not well-suited to satisfy this use-case in an scalable way.
Shallow navigation has been one of the two rough corners I have found while developing with SvelteKit (with the other being lack of proper, composable dependency injection. See https://github.com/sveltejs/svelte/discussions/15225).
While this is definitely a minor annoyance given than most of the time you would be handling simple state (say an object with 1-3 keys) I think the lack of proper typescript and composability should be addressed at some point.
Describe the proposed solution
Any of these solutions would provide a better DX working with page.state:
- Allow exporting a
PageStatetype from+page.tsand+layout.tsfiles so we can castpage.statelike the following:
<script lang="ts">
import {page} from '$app/state';
import type {PageState} from './$types.js';
const state = page.state as PageState;
</script>
- Solution 1 + outright export
page.statefrom'./$types.js'so we can do the following:
<script lang="ts">
import {state} from './$types.js';
</script>
- Rethink
state: allow exporting a state function from+page.ts/+layout.ts files. Then makestateavailable as a top level prop returned by the$propsrune. Thestateprop is typed the same way as thedataprop andreplaceStateis called before rendering the route sostateis populated with its initial value before it is used.
//+page.ts
//State might be an async function and has access to the parent state function and its sibling load function.
//We may want to allow exporting a state type instead of function in case we don't want an initial state but still want a typed state prop
export const state = async ({parent, load})=>{
const user_preferences = await load();
return {
last_video: {id: '1', time: 1.50, volume: user_preferences.volume}
};
}
export const load = async ()=>{
return {volume: 0.75};
}
<script lang="ts">
import type {PageProps} from './$types.js';
const {data, state}: PageProps = $props();
</script>
Proposals are ordered by the amount of required changes.
Proposal 2 would be the most controversial since it lacks semantic coherence (file named types is exporting data) but more ergonomic (no need to manually cast page.data).
While proposal 3 is my favorite solution since it does not only address the type issue but makes state composable, I understand that it is quite a big change for a small gain and that being able to access state through the $props rune and through the '$app/state' import is not ideal. This might be something to consider for a major release though.
The PageState type should follow the same load function return type rules: it should be shallowly merged with the parent +page.ts / +layout.ts PageState type by default and it should be available to all subroutes.
Alternatives considered
No response
Importance
nice to have
Additional Information
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the current page.state typing and the proposed entry points in +page.ts, +layout.ts, './$types.js', and '$app/state'. Compare the three proposed designs and determine the intended parent-state merging and subroute behavior. Done means an agreed, documented type design with its impact on page props and generated types established.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend, web-dev
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100