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

Before and after hooks for routing #552

Closed
antony opened this issue Jun 16, 2020 · 71 comments · Fixed by #3293
Closed

Before and after hooks for routing #552

antony opened this issue Jun 16, 2020 · 71 comments · Fixed by #3293
Labels
feature / enhancement New feature or request p1-important SvelteKit cannot be used by a large number of people, basic functionality is missing, etc. router

Comments

@antony
Copy link
Member

antony commented Jun 16, 2020

Describe the bug
onMount is not fired on page change, if the actual page component doesn't change. This is all routes which contain [dynamic] parameters, since the underlying components are the same.

To Reproduce
Reproduction here:

https://github.com/beyonk-adventures/sapper-onmount-issue

The colour pages set the value from the path parameters on load, and onMount sets the value in the page. Because onMount isn't refired, the colour never changes when clicking between pages, only on hard refresh, or when visiting the home page and then clicking a new colour.

Essentially it means you can't change the content of the page unless you visit an url which results in a completely different page component being loaded.

Expected behavior
Though using the same physical page component, /different/routes and /different/paths which map to /different/[parameter].svelte should fire onMount, since the route is changing.

Information about your Sapper Installation:

  • Sapper └── sapper@0.27.16

Severity
Extremely High. I don't actually know how to work around this.

Additional context
Fixing this would be a breaking change, but I think it's far more logical an approach than the current behaviour.

@Conduitry
Copy link
Member

The component's not re-mounting though. You can listen to changes to the $page store with reactive statements, which is how I think you're supposed to deal with this. I don't think this is a bug and I don't think Sapper should start destroying the old page component and creating a new one when switching between pages that fall under the same route.

@antony
Copy link
Member Author

antony commented Jun 16, 2020

This seems very odd to me. If my page preloads content, say I'm writing a blog, and that blog has links to related content:

https://example.com/blog/fish/how-to-feed-a-fish
https://example.com/blog/fish/how-to-eat-a-fish
https://example.com/blog/horse/how-to-feed-a-fish-to-a-horse

If my categories are hardcoded:

/blog/fish/[title].svelte
/blog/horse/[title].svelte

If I were to click on a link to a related blog about fish, I'd end up not calling onMount (and therefore not loading the content for the new blog)

If I were to click on a link to a related blog about horses, I'd end up calling onMount (and therefore loading the content for the new blog)

This feels like a seemingly innocuous set of links that do radically different things, and exhibit radically different behaviours, despite appearing identical to both the end user and the developer.

I've added the tag docs to this, but I'm not sure how I'd document it. What would be the rules regarding whether switching route does or doesn't require a subscription to the page store in order to load it's content?

I think this behaviour is mysterious, and (at least for us) has caused quite a serious problem in that pricing for one item remains on the page when you click a new item, since it wasn't obvious that I would need to listen to the page store in order to notify the component that a new page had loaded. It seems like a bit of a gotcha.

@Conduitry
Copy link
Member

I feel like part of the problem here is that onMount probably isn't the right place to be loading blog content. If you load it in preload, that will get re-run even on navigation to the same page component, and all the props will get their values updated. onMount also prevents the content from being SSR'd.

@antony
Copy link
Member Author

antony commented Jun 16, 2020

I think this problem manifested itself because the pricing indicator uses a store, so it's that store which needs to have it's value re-evaluated.

The content is loaded in preload (as per the demo), but once you click a new link, the assigning of a value to it's display variable (emulating population of a store) doesn't happen. I also tried with a reactive variable but the updated content didn't trigger an update.

I'll see if I can reflect the $page idea in the repro above to see if that improves things.

@benmccann
Copy link
Member

This works differently than I'd expect as well. Though onMount comes from Svelte and maybe we need Sapper-specific APIs for this? There are a lot of open issues around routing and page transitions (e.g. sveltejs/sapper#902, https://github.com/sveltejs/sapper/issues/814, sveltejs/sapper#1083, sveltejs/sapper#518, sveltejs/sapper#295, sveltejs/sapper#674). It seems like it'd be good to consider all of these together and figure out what API changes if any are needed to address most of the use cases

Rich discusses a lot of the issues raised here in sveltejs/sapper#295

@antony
Copy link
Member Author

antony commented Jun 17, 2020

I'm trying to work around this using the page store, and the problem is becoming clearer - I don't believe the current behaviour makes any sense. I have a shared component on my page which builds a bunch of stores and puts them in the context.

Setting the context can only be done on initialisation, and if the component doesn't re-initialise (since it's not re-mounted between route-changes), I can't change the items inside the context.

If I'm loading in content and passing in a store to a component, then I don't think there is a reasonable way to switch-out that store, which leaves me in a position that I'm unable to change that component's content.

I've updated the repro app to use the store. The logic seems solid the app straight-forward, but the behaviour just seems wrong. There might be a way to fix it by tying to the page store (which I will attempt next, but if there is, this really doesn't seem like obvious behaviour)

@antony
Copy link
Member Author

antony commented Jun 17, 2020

After trying to find a reasonable way around this now, I'm 99% erring on this behaviour being a bug / broken feature.

Not having the ability to use onMount and onDestroy means you have to write your own change logic for every component and sub-component which sits underneath your top-level component.

Listening for the $page store to change is fine if you're only one component deep, but the amount of subscribing/reactive declarations you need to get everything in the hierarchy to update just becomes unmaintainable very quickly.

Being able to rely on components mounting and un-mounting is the only sensible way I can think here of restoring state, since if you think about it, a route change is akin to "tearing down" the page component and "mounting" a new page component.

@benmccann
Copy link
Member

benmccann commented Jun 17, 2020

I mostly agree this isn't the best default as does Rich (in sveltejs/sapper#295 he says "I would argue that destroy/recreate should be the default behaviour")

My main question though is if we want to support both behaviors longer-term, how would we do that? I can think of a few options:

  1. Say that onMount and onDestroy are always called for the last part of a route and if you want something that persists across routes then you should put it in the layout common to that route segment which will be persisted instead of recreated
  2. Say that the user must handle it via pushState and updating the UI on their own
  3. Let onMount and onDestroy operate as they do today, but introduce new APIs like onNavigateTo and onNavigateFrom
    1. That would also allow us to support route guards (though beforeunload might be a better solution for that). E.g. Routify has beforeUrlChange and afterPageLoad. Vue Router has beforeRouteEnter, beforeRouteUpdate, and beforeRouteLeave

Btw, I think the $page store is undocumented because I can't find "$page" in the docs

@antony
Copy link
Member Author

antony commented Jun 17, 2020

Ah that's a very good spot. I'm absolutely certain that the "method of least surprise" is a route change calling create/destroy.

However I realise this is a breaking change, so we have to decide if we leverage the fact that sapper is pre 1.0 and make this breaking change with a sensible default.

Page store is documented in Svelte and in Sapper (preloading)

I don't feel personally that we need new APIs. I think we can possibly add a flag to the <a> tag which will allow a consumer to not have the old page torn down and recreated, if they really want that behaviour.

it's worth noting that if you use a router which uses <svelte:component> which is a large number of Svelte routers, I think, you do get the onMount and onDestroy calls.

@benmccann
Copy link
Member

If you go from /a/b/c to /a/b/d would that only call create/destroy on the last segment and not on the layouts for /a and /b?

We have a couple other PRs for breaking changes as well. I think it'd be nice to have a 0.28.0 that merges any outstanding breaking changes. Sapper doesn't feel like it's ready for a 1.0 yet, and I think locking in all the current behaviors and APIs might be a bit premature.

@antony
Copy link
Member Author

antony commented Jun 22, 2020

After playing with this for a few days, this is definitely a bug. It might be intentional behaviour, but forcing the user to manage the component lifecycle because they visit two routes which use the same component isn't desirable, it's unpredictable, and it's also extremely difficult since you have to do all sorts of manual checking of state.

My belief is that the most sensible behaviour is that route changes should always cause an onMount and onDestroy, unless explicitly overridden.

I don't think anybody would complain if this behaviour was changed for version 0.28. I believe it is the "API changes" we talk about in the docs and README. It also shouldn't break any existing sites with this as the default, since it's just doing a little extra work, and not removing anything.

@Rich-Harris
Copy link
Member

as does Rich (in sveltejs/sapper#295 he says "I would argue that destroy/recreate should be the default behaviour")

"A foolish consistency is the hobgoblin of little minds" — Emerson

I very vaguely recall some conversations about this issue as we switched to <slot>-based layouts way back when, and FWIW the current behaviour definitely was deliberate. The thinking was that aside from being more efficient (since you're not recreating a bunch of stuff only some of which, possibly very little, has changed), it makes it much easier to create appealing page transitions when you're navigating between different pages belonging to the same route:

http://aboard-parent.surge.sh/conditions/green

needle

It's true that we haven't really capitalised on this ability, or done a good job of communicating it. But that's my argument in favour of keeping things as they are. It does make some things a bit harder, but in return it makes other things possible.

@Rich-Harris
Copy link
Member

Having said that, I do concede that there's a precedent for exposing options for this sort of thing as attributes on <a> elements (sapper-noscroll, rel="prefetch"). So it could be done there (and maybe rel="prefetch" should become something like sapper-prefetch while we're at it).

But I'd still argue that the current behaviour should be the default and not the option, since a) it's the more efficient way, b) it's working as components ordinarily work (though of course people have asked for an equivalent of React's key for components in the past), and c) I think it's a cool differentiating feature that AFAIK you don't get with other frameworks.

If you go from /a/b/c to /a/b/d would that only call create/destroy on the last segment and not on the layouts for /a and /b?

Definitely think we want to keep this behaviour the same, i.e. layout components persist — I can't imagine a situation where you'd want to recreate the layouts from scratch (and consistency with that is perhaps another argument for keeping the behaviour of the leaf component as it is).

Side-note: While writing this comment I came to regret my giant flashing GIF.

@antony
Copy link
Member Author

antony commented Jun 24, 2020

I'd be happy with this suggestion, as long as there was some way to make the onMount and onDestroy hooks get called. I can understand the reason behind wanting to be efficient with creating/destroying components.

However I will maintain, and with good reason, that this behaviour is a gotcha and is not obvious. It really doesn't make sense to me that I would have to know an intimate, internal rendering concern of Sapper to determine whether certain parts of my page's content will end up with updated state or not, depending whether the route I came from is the same, or a different physical component. It's doesn't follow Principle Of Least Astonishment. For this reason I'd suggest that this feature/rendering optimisation/gotcha is documented quite clearly.

Side-note: Absolutely regretting your gif.

@Conduitry
Copy link
Member

Those are both Svelte things though that are used to call code when the component is created and destroyed, and the component is not getting created or destroyed. We should document this, yes, but we shouldn't give Sapper a way to circumvent intended Svelte behavior (nor, I think, could we, without having Sapper use undocumented Svelte APIs). Is there anything that you can't do with a reactive block subscribing to the $page store?

@antony
Copy link
Member Author

antony commented Jun 24, 2020

My use case is quite complicated, so it's difficult to really recreate in a simplistic way, but I'll try. Essentially if you're doing any work in onMount or the initialisation block of the page, this work won't be done for the new information when the route changes, so you can end up with stale data on your page, which is completely unexpected and hard to track down.

(In a simplistic form, you could use the $page store to make this data reactive again, but it's completely unexpected to have to do that - it's not something you'd do automatically, you'd have to know that a route change wouldn't re-run your intialisation code unless you were route-changing from a different page component)

@Rich-Harris
Copy link
Member

Does 'unexpected' mean 'undocumented', or would this still trip people up even if it was covered well in the docs (and any future tutorial/guide we produce)?

we shouldn't give Sapper a way to circumvent intended Svelte behavior (nor, I think, could we, without having Sapper use undocumented Svelte APIs)

We could do it like this, I think:

const OldLeaf = Leaf;
Leaf = null;
await tick();
Leaf = OldLeaf;

If we were to add this feature as a sapper-* attribute on links, what should the attribute be called?

@antony
Copy link
Member Author

antony commented Jun 24, 2020

it's going to trip people up regardless, because people are bad at reading docs (counting myself here). However I have less sympathy for people who trip because they haven't RTFM'd.

I am still strongly in favour of adding it as an attribute as suggested. Is sapper- the best way or is sapper: a more consistent prefix? Is sapper:prefetch = rel="prefetch" reasonable?

Either way, I think remount is descriptive enough, so something like:

sapper:remount or sapper-remount

Edit: I've just realised, sapper: won't work as this is part of the Svelte API right? So sapper-remount perhaps.

@Rich-Harris
Copy link
Member

I like sapper-remount 👍

@benmccann
Copy link
Member

A few things to think about:
If we put it on the link then the user has to make sure to add it to every link to the same route. That seems potentially error-prone as I imagine it'd be easy to miss one and would need to be considered every time a new link is added
How would programmatic navigation be handled?
It doesn't allow the user to have a handler both for the first time the component is mounted and for when the page is navigated to.

The gif example in my view is a pretty rare use case and I'm not sure it's the one I'd choose to optimize the API for. If we always destroyed the old component and mounted a new one on navigation (which is more in line with what I'd expect to happen), I think the user could still build the behavior in the gif within a single component by using pushState

In any case, if we want to keep onMount working as it does today, I think something like Routify's beforeUrlChange would be a friendlier way of handling this. Then you only need to set it up once instead of making sure you consider whether every link on the page needs a new attribute. It also makes it easier to have both types of handlers instead of choosing either/or

@Rich-Harris
Copy link
Member

If we put it on the link then the user has to make sure to add it to every link to the same route.

That is a concern, yeah. It's a feature of the component rather than links to the component, but we don't have a great way to express that in-band.

How would programmatic navigation be handled?

app.goto(url, { remount: true });

The gif example in my view is a pretty rare use case and I'm not sure it's the one I'd choose to optimize the API for.

This might just be me tilting at windmills, but to me the fact that it's a rare use case is itself the problem. We web folk have trapped ourselves in the 'pages' mindset, where navigation represents a jump from one discrete thing to another. In native apps and in more pie-in-the-sky sources of inspiration, navigation is often more like exploring space or exploring an object, in a way that emphasises fluidity and continuity and more closely resembles how our brains model the real world.

It's true that my example is slightly contrived — I cobbled it together fairly quickly last night — but I don't think it's so hard to come up with better real world ones. Think about a photo album app where you zoom in from the grid view to an individual photo when you tap it, or where the pixel average background colour behind the photo transitions smoothly, or an ecommerce site like this except with similarly appealing transitions when you navigate between alternatives rather than going from index to detail view, or really any app where you're actually navigating through a space (something like this Sketchfab example, except that the 'annotations' are actually URLs).

I think the user could still build the behavior in the gif within a single component by using pushState

We probably want to avoid cornering developers into using the history API directly, since Sapper is managing the history — it's a likely source of bugs. At the very least I think we'd want to wrap that API (i.e. handle via app.goto).

In any case, if we want to keep onMount working as it does today, I think something like Routify's beforeUrlChange would be a friendlier way of handling this.

Actually yeah, lifecycle functions might be the correct way to handle this. I don't think they need to be implemented as stores as they have been in Routify, they could just be normal custom lifecycle functions AFAICT.

@antony
Copy link
Member Author

antony commented Jun 24, 2020

Actually yeah, lifecycle functions might be the correct way to handle this.

Would this mean that I would have to consider use of onMount in page components, instead preferring (say) beforeUrlChange, since only one is guaranteed to run when a page loads?

@benmccann
Copy link
Member

think about a photo album app, ... an ecommerce site like this, ... something like this Sketchfab example

Good examples! The way I had been thinking this would work if we did remount each component upon navigation is that you would have a layout component (potentially with no UI and just code if that's all you need) that could have it's own onMount. That would end up being run the first time you navigate to the photo album, for example, but not when opening an individual photo. I think that would end up covering both the traditional page-based use case Antony raised as well as these more interactive use cases

Would this mean that I would have to consider use of onMount in page components, instead preferring (say) beforeUrlChange, since only one is guaranteed to run when a page loads?

My thinking was that you would use the one appropriate for the usecase. For the usecase you have where you want it to run everytime a page loads then you'd put the code in beforeUrlChange. If you want something to run just once when first loading the component, but not for individual page navigations, then you'd put it in onMount

@Rich-Harris
Copy link
Member

Yeah, I'm envisaging something like this, perhaps:

// @sapper/app
import { onMount, tick } from 'svelte';

// ...`stores`, etc

export function onNavigate(fn) {
  const { page } = stores(); // this works because of when `onNavigate` is called
  
  onMount(() => { // ditto
    const unsubscribe = page.subscribe(async $page => {
      await tick(); // so that it matches `onMount` semantics following subsequent navigations
      fn(); // or fn($page)?
    });

    return unsubscribe;
  });
}

dimfeld referenced this issue in dimfeld/website Jun 24, 2020
…nt is not recreated

Addresses sveltejs/sapper#1278
@benmccann
Copy link
Member

benmccann commented Jun 24, 2020

Could we also add a function like onLeave or onBeforeNavigate as well? Would it just call fn() without calling tick() first? I haven't used stores much yet...

@PatrickG
Copy link
Member

PatrickG commented Jun 25, 2020

Yeah, I'm envisaging something like this, perhaps:

// @sapper/app
import { onMount, tick } from 'svelte';

// ...`stores`, etc

export function onNavigate(fn) {
  const { page } = stores(); // this works because of when `onNavigate` is called
  
  onMount(() => { // ditto
    const unsubscribe = page.subscribe(async $page => {
      await tick(); // so that it matches `onMount` semantics following subsequent navigations
      fn(); // or fn($page)?
    });

    return unsubscribe;
  });
}

That would never unsubscribe the onMount listeners, would it?
Also, I've created a PR about 6 months ago (sveltejs/sapper#1047), with a onNavigate function to prevent navigation. Maybe this could also resolve this issue.

Or maybe just export a constant in the module script block, like the preload function, to tell sapper that this component should remount.

<script context="module">
  export const REMOUNT = true;
</script>

Then the normal onMount/onDestroy functions would be called on remount with this:

We could do it like this, I think:

const OldLeaf = Leaf;
Leaf = null;
await tick();
Leaf = OldLeaf;

@jokin
Copy link

jokin commented Jul 2, 2020

This might just be me tilting at windmills, but to me the fact that it's a rare use case is itself the problem. We web folk have trapped ourselves in the 'pages' mindset, where navigation represents a jump from one discrete thing to another. In native apps and in more pie-in-the-sky sources of inspiration, navigation is often more like exploring space or exploring an object, in a way that emphasises fluidity and continuity and more closely resembles how our brains model the real world.

You might cobbled your example quickly, but in my use case it totally makes sense the way it works right now. I'm developing a TV ui, which has to include and initialize the video tag on reaching the /tv/channel path, and just interact with the tag when you switch channels to /tv/channel2. Subscribing to the page store to get the updated channel number is enough for this case, destroying and recreating everything would be excessive, and in my case that runs on very low performance chips, a dealbreaker.

@natevaughan
Copy link

natevaughan commented Jul 27, 2020

IMO this is 100% a bug and the fact that it accidentally has some benefits is irrelevant. Let me try to convince you all:

The problem is that the developer now has to consider not just where a user is but how she got there (or where she is coming from) to reason about their app. Consider @antony's example where there are 3 views:

https://example.com/blog/fish/how-to-feed-a-fish
https://example.com/blog/fish/how-to-eat-a-fish
https://example.com/blog/horse/how-to-feed-a-fish-to-a-horse

And the dynamic params at the top level:

/blog/fish/[title].svelte
/blog/horse/[title].svelte

This example is very plausible if it is e.g. team and user instead of fish and horse. If the user navigates from horse 1 to fish 1, she will be in one state (new content loaded). If a user navigates from fish 1 to fish 2 directly, she will be in a different/inconsistent state (no new content loaded). But if the user who got to fish 2 via fish 1 refreshes her browser, she'll see different content.

Consider these two functions:

function navigateToFish(title) {
   goto(`/blog/fish/${title}`);
}

function navigateToFish2(title) {
   window.location = `/blog/fish/${title}`;
}

I'd argue that they should behave exactly the same way. The user is navigating to the same view; the developer took the recommendation to use Sapper's goto when handling navigation. But if the developer uses the first function on any fish pages, the two functions will not behave the same way.

IMO the URL represents where the user is regardless of how they got there, and Sapper should default to idempotence: a request produces the same result every time.

To address the examples of a photo album or ecommerce site, I see those as performance optimizations. You presumably know where the user is coming from (another photo or another item of a certain type) and going to, so you want to optimize the transition and possibly add an animation. Sure! Why not just make {remount: true} the default and if you happen to know you're going from one photo to another, you can optimize by passing {remount: false}?

@pngwn
Copy link
Member

pngwn commented Jul 27, 2020

The problem is onMount is a svelte thing. And the component isn't remounting. So it definitely isn't a bug, 100%.

It might be confusing, it might be undesirable to some extent, but it isn't a bug.

Changing the core behaviour would definitely break stuff that I have built, no question, it would also make those apps impossible to build with sapper. I would personally consider remounting on every route change a step backwards.

I have little opinion on whether or not a way to force a remount would be good for sapper. I see no harm in providing that option if people need it.

@f4ww4z
Copy link

f4ww4z commented Feb 7, 2021

@Rukkaitto That doesn't seem to work for me. I want to refresh my auth token on any route change. Here's my code so far:

<script>
  import { stores } from "@sapper/app";
  import { onMount } from "svelte";
  import Nav from "components/Nav.svelte";
  import Footer from "components/footer.svelte";
  import Sidebar from "components/sidebar.svelte";
  import { jsonHeaders } from "./server/_helpers";
  // export let segment;
  let sidebarShow = false;

  const { session, page } = stores();

  const refreshToken = async () => {
    // only refresh when signed in (token is not empty)
    if (!!$session.token) {
      console.log("Old token: " + $session.token);

      const res = await fetch("/server/auth/refresh", {
        method: "POST",
        headers: jsonHeaders({}),
      });

      const result = await res.json();
      const { token } = result;
      $session.token = token;

      console.log("Refreshed token: " + $session.token);
    }
  };

  $: if (page) {
    refreshToken();
  }
</script>

I place it at _layout.svelte .

Anyone got a solution? This is working only when I refresh the page.

I also agree with the OP of this issue. Maybe create an equivalent of onMount that's triggered on goto's and redirect's ?

@sswam
Copy link

sswam commented Feb 7, 2021

This just bit me when implementing a /user/[id] route. The admin user can edit details of any user, and a normal user can edit their own details.

When I click from editing a user to the "settings" link so that admin to edit own details, nothing happens, due to this issue. It's only by good luck that I happened to click in that order and noticed this at all.

Information security and privacy is more important than efficiency, and I don't want there to be any possibility that residual data from editing one user can accidentally transfer over to when I am editing another user. The natural expectation is that onMount should be called each time I go to a different route or URL, including different parameters. Each route or virtual page should be a fresh instance of the page without any data left over from the previous route.

I think that this safe and unsurprising behaviour should be the default, or at least we need some option to make it the default. As it is, I'm not too sure how to fix my page. I didn't design it thinking that its parameters might stealthily change underneath it.

Is there some function I can call to remount the page when I detect this circumstance? In addition to just making it work, I am concerned about the possibility of information leaking between the different user pages, so I'd rather "reload" the page than attempt to make it adjust everything when the parameters change, but I don't want to call location.reload() if there's some other option.

I haven't seen any general-purpose and concise working solution above. My current "solution" is an ugly hack:

<script>
        // Detect when sapper is changing the page parameters underneath us, and reload the page.
        // https://github.com/sveltejs/sapper/issues/1278
        let original_path;
$:      if (original_path && $page.path != original_path) reload_page();

        async function reload_page() {
                const hash = window.location.hash;
                console.log("reload_page", original_path, $page.path + hash);
                await goto('/', {replaceState: true});
                goto($page.path + hash, {replaceState: true});
        }

        onMount(async () => {
                original_path = $page.path;
                // ...
        }

        // ...
</script>

@benmccann benmccann changed the title onMount is not fired when changing route, if the route changes only dynamic parameters Before and after hooks for routing Mar 16, 2021
@benmccann benmccann transferred this issue from sveltejs/sapper Mar 16, 2021
@benmccann benmccann added feature / enhancement New feature or request router labels Mar 26, 2021
@benmccann benmccann added the p1-important SvelteKit cannot be used by a large number of people, basic functionality is missing, etc. label Aug 5, 2021
@samuelstroschein
Copy link
Contributor

I just bumped into the same problem. I read that changing the behavior is a breaking change. This issue is not on the 1.0 milestone though. Shouldn't it be on the 1.0 milestone?

I bumped into the problem by loading data based on a page slug in the onMount function. I used goto to route to a different page slug e.g. another project id and expected the data to update, which did not happen due to this issue.

@chanced
Copy link

chanced commented Sep 9, 2021

... it makes it much easier to create appealing page transitions when you're navigating between different pages belonging to the same route

I totally understand where you're coming from here. I agree 100% that the web has a serious disconnect from other forms of applications when it comes to user experience. The web is, by and large, devoid of continuity and that is unfortunate. There are some legit reasons for this but hopefully we are at, or nearing, the point where this can be rectified.

Your example looks great, especially for such a quick spike, by the way. I think it illustrates what we've been missing in this space. However, it is also restricted to the same page/route. If the expectation is for the web to evolve and become more in line with native experiences, then we need to look at the application as a whole, yea? Presumably, most applications are going to operate with a lot more than a single route.

This means that for that sort of experience, the optimizations of component mount cycles either can't be a deciding factor or they need to be moved up a layer. I think layouts are often over-used but this seems to sit squarely in their domain. A page shouldn't be expected to know how to transition to every other page.

There is an expectation, rightfully or otherwise, that a "Page" is an isolated, end-to-end process. By having circumstances where hooks are not fired upon is alien to the terrain. It seems counter intuitive to need to employ defensive programming to ensure those lifecycle events occur or have occurred.

Now, having said all of that and setting aside the performance improvements, there are usecases for the current behavior. The best example that comes to mind is infinite scrolling, where the current cursor is represented in the URL. However, in that case, it probably makes more sense to avoid load as well. I'd prefer to be able to explicitly opt-out of those life cycle events, even if that is a primitive implementation such as window.location = `/photos?cursor=${cursor}`;.

</twocents>

@lsabi
Copy link

lsabi commented Nov 3, 2021

I've encountered the same problem....what's the status of this "bug"?

Just to give an idea of another scenario, this is my use case:
I've a breadcrumb for the category tree which does not get updated, but it shows on the bottom-most category the items (or subcategories) with that category when navigating through pages. Both category tree and items are taken from the same load function. Sometimes the items export will be an empty array, sometimes it will be an array populated with objects.

@benmccann
Copy link
Member

I've encountered the same problem....what's the status of this "bug"?

Please don't ask questions like this as it doesn't add anything except noise to our inboxes. I'd rather spend time contributing to the project than managing my inbox. The issue is open, which indicates the status. Anyone who would like to take a stab at implementing this is free to send a PR

@ric2b
Copy link
Contributor

ric2b commented Dec 10, 2021

Given how many people find this behavior counterintuitive and the decision to keep the behavior it'd probably be a good idea to add an entry to the FAQ explaining why it happens and some workaround recommendation (or a link to this issue, which has a few workarounds).

@vishalbiswas
Copy link

Hey, I see that this issue was closed but I couldn't figure out what was the resolution?
I see the linked PR but I don't understand how that fixes this issue.

Secondly, I have a little problem with the suggested load() function workaround when using TypeScript.

I have a variable stats that is assigned times during the lifetime of the component (first assignment being on route load).
Something like this:
let stats = getStats();

It uses props data and component data. Doing it this way, I don't need to explicitly specify the type of stats. Now if I have to do it inside load() function then

  1. I will have to import the getStats function in both script blocks.
  2. I will not have automatic type assignment for stats variable and have to declare its type explicitly

@benmccann
Copy link
Member

The maintainers have been having some discussion about whether a new component should be created when you navigate to a new URL corresponding to the same route and are looking for input from the community: #5007

frederickobrien added a commit to gonzo-engineering/teeline-online that referenced this issue Feb 16, 2024
Addresses a longstanding workaround where I was dropping in links between lessons in the svx files rather than at the [slug] level. This was due to a bug/feature (see here for context: sveltejs/kit#552) where the links were getting all messed up. By wrapping the component in a `#key` logic block, ensuring it's recreated any time the slug changes, they don't any more. Best practice? Who knows, but it seems to work
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature / enhancement New feature or request p1-important SvelteKit cannot be used by a large number of people, basic functionality is missing, etc. router
Projects
None yet
Development

Successfully merging a pull request may close this issue.