Search code examples
sveltetanstackreact-query

How do I fetch a post by ID without API and stop calling the API every time I navigate back to it?


Stack

Setup

I have two different pages (HTML omitted):

  1. posts/+page.svelte
  2. post/[id]/[title]/+page.svelte

I use TanStack Query for storing data from the API. While I reference Svelte, I also use the documentation for React.js as most syntax seems to be compatible.

Here are the two major problems I am facing:

  • In the posts page, I fetch all posts as an array. It fetches all data, but every time I navigate to this page, an API call is fired. I don't want to make redundant API calls. How can I prevent this behavior?

  • In the [id]/[title]/ page, I want to fetch post data from the already fetched data from the API, but the post is undefined. This problem is more serious. How can I log post from cached data?

Here is the simplified code for the two files:

src/routes/posts/+page.svelte

<script lang="ts">
  import { onMount } from 'svelte';
  import { createQuery } from "@tanstack/svelte-query";
  import type { Post } from "$lib/models/Post";
  import { fetchPaginatedPosts } from "$lib/posts/fetch-pagination";
  import { data } from './store';

  // Create the query outside of the render context
  const query = createQuery({
    queryKey: ["posts"],
    queryFn: fetchPaginatedPosts,
    initialData: null,
    refetchInterval: 360000, // Using the intervalMs from the store if needed
  });

  // Subscribe to the data store and update it with the query result
  onMount(() => {
    data.set($query);
  });

  let posts: Post[] = [];

  $: $data = $data || $query.data;

  $: {
    if ($data && !$data.isLoading && !$data.isError && $data.data) {
      posts = $data.data.posts || [];
      console.log("call without API", posts);
    }
  }
</script>

src/routes/post/[id]/[title]/+page.svelte

  <script lang="ts">
  import { page } from "$app/stores";
    import type { Post } from "$lib/models/Post";
    import { QueryCache } from "@tanstack/svelte-query";
    import { onMount } from "svelte";
  
    const queryCache = new QueryCache()
  
  const query = queryCache.find({ queryKey: ['posts']})
  
  let posts: Post[]
  let post: Post
  $: id = $page.params.id;

  onMount(()=>{
    posts = $query.data.posts
    post = posts.find(post.id === id)
  })
  onMount(()=>{
    console.log(post)
  })
  </script>

After getting answer:

src/hooks.server.ts

import type { Handle } from '@sveltejs/kit';
import { fetchPaginatedPosts } from '$lib/posts/fetch-pagination';
import { queryClient } from '$lib/posts/queryClient'; // Assuming you have a separate queryClient instance
import { fetchedPosts } from '$lib/posts/store';
import type { Posts } from '$lib/models/Post';

export const handle: Handle = async ({ event, resolve }) => {
  if (event.url.pathname.startsWith('/posts')) {

    const cachedData = queryClient.getQueryData(['posts']);

    if (!cachedData) {
      const posts = await fetchPaginatedPosts();
      queryClient.setQueryData(['posts'], posts);
        fetchedPosts.set(posts)

    }else{
        fetchedPosts.set(cachedData as unknown as Posts)
  
    }
    
  }


  const response = await resolve(event);
  return response;
};

Solution

  • You can use sveltes Middleware (hooks.server.ts). In the middleware get the data you need, and check if the correct data was retrieved for the page if not run it again.