-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Provide access to history state #5478
Comments
One thought I just had: the user might scroll or interact with form elements while the navigation is in progress. I wonder if we need a way to write to state immediately before the navigation is committed |
Just to connect the two: This would also help with #5478 by giving people easier access to scroll positions. |
Just start working on this. Possible solutions:
Getters and setters sounds better I think Any ideas on this? |
getState and setState make it easier for us but I'm not sure if I like it Design-wise, doesn't feel like something we do in other places. Either way we need to make sure that it's only possible to change state inside the hook. |
Why only in the hook? We can only provide the state in the page store and not the actual object in hooks like:
That may be easier for us, and the in client we do:
|
This sounds appealing at first, but what if the user presses the back button, what should happen to all the state that was updated outside of the navigation hooks? By keeping it scoped to them, we don't have to answer that question |
The state that users may change is kept in a When user clicks back button happens the following:
We update the page store between |
What if people do |
If someone mutates the state like EG: |
We definitely only want to set state at the instant we're navigating to another page (the exception being cross-document navigations, where we need to do it right before unload). Nothing else would really make any sense. Per #5478 (comment), we can't actually use onSnapshot(() => {
return {
sidebar_scroll: sidebar.scrollY
};
}); The snapshot would be available in afterNavigate(({ from, to, snapshot }) => {
sidebar.scrollY = snapshot?.sidebar_scroll;
}); I like the name |
Thinking more about the modal navigation case, I think it's probably a separate concept than
<script>
import { page } from '$app/stores';
import { goto } from '$app/navigation';
</script>
<button
on:click={() => {
goto($page.url.href, {
state: {
modal: true
}
})
}
>
show modal
</button>
{#if $page.state.modal}
<div class="modal">...</div>
{/if} |
@Rich-Harris Does it totally fall off, will it not be considered? |
Seems very hard to understand, honestly. A lot more complex — semantics get very fuzzy around layouts and |
@Rich-Harris My
layout and load functions etc - is fuzzy(truth), but only in the way that it uses As I write such things, I assume that you, as authors and members, know SvelteKit incomparably better than I do, you know it inside out. I assume that I may not describe something 100%, because I will not predict something. I'm afraid (this is my personal feeling and experience) that due to the fact that I am an outsider, there is a certain reluctance to what I will write, even though the content can be, for example, realized one to one after a year and a half (e.g. this sveltejs/svelte#5797 (comment)), and even the justification is as there. As for flexibility - maybe. I was only thinking about modal navigation. I don't know what are the scenarios where The |
Realised the onNavigate(
({ snapshot }) => {
sidebar.scrollTo(0, snapshot?.sidebar_scroll ?? 0);
},
() => ({
sidebar_scroll: sidebar.scrollY
})
); ...except that it's not clear how to associate a particular snapshot with a particular component in a way that survives the component's destruction. If there was only one snapshot for a given history entry that wouldn't be a problem, but you might have a situation like this: // src/routes/foo/+layout.svelte
onNavigate(
({ snapshot }) => {
console.log(snapshot?.layout_state); // 1
console.log(snapshot?.page_state); // undefined
},
() => ({
layout_state: 1
})
); // src/routes/foo/+page.svelte
onNavigate(
({ snapshot }) => {
console.log(snapshot?.layout_state); // undefined
console.log(snapshot?.page_state); // 2
},
() => ({
page_state: 2
})
); |
Couple of different thoughts:
|
To clarify, this doesn´t work in ssr right? |
Correct,
I think we have to treat this as a last resort — the less we have that sort of coupling the better.
This wouldn't solve the problem of navigating offsite then hitting 'back'. It's also a guaranteed source of memory leaks, unfortunately. It occurs to me that we could reliably associate snapshots with components if we restricted it to page/layout components. Essentially we do this: <!-- src/routes/some/+page.svelte -->
<script>
export const snapshot = {
capture: () => sidebar.scrollTop,
apply: (y) => sidebar.scrollTo(0, y)
};
</script> It would be an unusual API but I can't think of any compelling alternatives. The nice thing about this is it's completely orthogonal to |
So it´s not suitable for modals in my case as i want to support them in ssr too. Any idea/plan how to implement that? I´m currently using searchParams, was just wondering if maybe there is a better (more sveltekit way) solution. |
Hi @david-plugge, did you find a solution? I'm looking to have a modal based route, and don't really have any idea where to start and ideally have SSR. Although guess could give up SSR if this issue makes it simple but don't know the timeline on this issue's priority. |
When modals is implemented, could they theoretically be implemented in the same way pages are? It would be really useful if modals could have a load function, action or snapshot. For us at least we usually use modals for forms. Having all those features baked in would be really nice. |
@ZetiMente completely missed your comment, very sorry. <script lang="ts">
import { page } from '$app/stores';
import Modal from './Modal.svelte';
$: showModal = $page.url.searchParams.has('modal');
</script>
{#if showModal}
<Modal />
{/if}
<a href="/?modal">open modal</a> But keep in mind you cannot properly use sveltekit actions at the moment when the path does not match the path of the action. |
I'm not sure whether i'm missing something, but I don't understand why this issue is still open since svelte-kit is not "overwriting" the whole history.state anymore. I'm able to accomplish everything I need with the following: // persist component state
history.replaceState(
{
...history.state,
state: {
...history.state.snapshot,
search: state
}
},
'',
window.location.href
);
// restore component state
const state = browser
? history.state?.state?.search ?? {}
: {} I personally feel this way of interacting with the history seems more flexible than snapshot API, I've tried to accomplish the same with it, but I ended up to the conclusion that is actually harder because snapshots are restored after component initialization while I can just read |
With shallow routing implemented, 90% of the things described in this feature request are available now:
The thing that's not there yet is manipulating state in |
Describe the problem
There are a number of cases where it's valuable to be able to read and write state associated with a particular history entry. For example, since there's no concept of bfcache when doing client-side navigation, things like scroll position (for elements other than the body) and form element values are discarded.
Associating user-controlled state with history entries would also make it possible to do things like history-controlled modals, as described in #3236 (comment).
Describe the proposed solution
When creating a new history entry with
history.pushState
orhistory.replaceState
(in user-oriented terms, navigating via a<a>
click intercept orgoto(...)
), we create a new empty state object and store it in a side table (using a similar mechanism toscroll_positions
, which is serialized tosessionStorage
when the user navigates cross-document).Reading state
After navigation, this object is available in
afterNavigate
......and in a store:
Writing state
So far, so useless. But we can write to the current
state
right before we leave the current history entry usingbeforeNavigate
:Then, if we go back to that entry, we can recover the state:
Programmatically setting state
You might want to show a modal that can be dismissed with the back button (or a backwards swipe, or whatever device/OS-specific interaction signals 'back'). Together with shallow navigation (the concept, if not the name), you could do that with
goto
:Then, a component could have something like this:
Closing the modal would cause a backwards navigation; a backwards navigation (triggered via the modal's 'close' button or the browser's chrome) would close the modal.
Alternatives considered
The main alternative is to attempt to automatically track the kind of state that people would want to store (i.e. scroll positions, form element values) so as to simulate the behaviour of a cross-document navigation with bfcache enabled. This comes with some real implementation challenges (capturing the data isn't trivial to do without risking performance issues, and there's no way to reliably determine equivalence between two separate DOM elements), but moreover I'm not certain that it's desirable. Things like automatically populating form elements can definitely go wrong.
One aspect of the design that I'm not sure about is whether the state should be a mutable object or an immutable one. Probably immutable (especially since the Navigation API, which we'd like to adopt eventually, uses immutable state), which makes me wonder if we need to expose methods for getting/setting state inside
beforeNavigate
andafterNavigate
rather than just astate
object.We might also need some way to enforce that state is serializable (most likely as JSON) so that it can be persisted to
sessionStorage
, so that it can be recovered when traversing back from another document. Then again perhaps documentation is the solution?Importance
would make my life easier
Additional Information
No response
The text was updated successfully, but these errors were encountered: