Send data from one page to another instead of preloading?
Asked Answered
S

1

6

Say I have a blog route that loads a complete array of all blog posts. The individual blog posts live at blog/[postId]. Is there a Sapper-idiomatic way to pass the data for the individual post from blog to blog/[postId]?

Essentially, if you're on blog I'd like to preload the code for displaying blog/[postId]. And then when you click a link to blog/[postId], navigate there instantly and display data from blog. But of course if you navigate directly to blog/[postId] then preload() should still be called.

prefetch doesn't quite do it because that still requires a network request. I also tried a preload that checks a store and doesn't issue the network request unless its empty, but you can't use stores in <script context="module">.

Sharolynsharon answered 13/2, 2020 at 5:58 Comment(2)
You could probably do that in your layout route. If that lives in your blog directory you can preload all posts in there and then according to each id you display that data. But I would probably just preload a single blog id and then once it's loaded fetch all the other posts and store them in a store.Fireworks
Thanks for the suggestion. It set me on the path to figuring it out. Turns out preload() always gets called on navigation, regardless of whether its in _layout.svelte, a regular page component, or both. I ended up storing the data in the second parameter to preload(), session, and only calling this.fetch() if it was empty or stale.Sharolynsharon
S
3

Thanks @three for your comment which led me to the answer.

If a route or _layout component exports preload(), it always gets called on navigation. So I ended up doing this by storing the fetched data in the second parameter to preload(), which is conventionally called session and appears to be shared among all components. And I only called this.fetch() if the data was not already present.

<!-- blog/index.svelte -->
<script context="module">
  export async function preload (page, session) {
    let {posts} = session;
    if (undefined === posts) {
      const response = await this.fetch('blog/posts.json');
      posts = await response.json();
      session.posts = posts
    }

    return {posts}
  }
</script>

<script>
  export let posts;
</script>

{#each posts as post}
  <!-- ... -->
{/each}

Then in [slug].svelte check session.posts for the post you're looking for, and if it isn't present, load it.

<!-- blog/[slug].svelte -->
<script context="module">
  export async function preload (page, session) {
    const {slug} = page.params
    const {posts} = session
    let post

    if (undefined === posts) {
      const response = await this.fetch(`blog/${slug}.json`)
      post = await response.json()
    } else {
      post = posts.find(p => p.slug === slug)
    }

    if (undefined === post) {
      this.error(404, "Sorry we couldn\'t find that post")
    }

    return {post}
  }
</script>

<script>
  export let post
</script>

<!-- ... render post -->

Now navigation is instantaneous. For ultimate robustness you could add checks to reload if the data were stale (say older than 5 minutes, whatever).

Edit

You don't actually need to be using sessions, per se. If you just want to make the session parameter to preload(page, session) not be undefined you can use this in server.js:

sapper.middleware({
  // Define session parameter as an empty object
  session: (req, res) => ({})
})
Sharolynsharon answered 15/2, 2020 at 5:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.