SvelteStack / svelte-query

Performant and powerful remote data synchronization for Svelte
https://sveltequery.vercel.app
MIT License
814 stars 31 forks source link

Is there a way to getQueryData() with one key only? #46

Closed frederikhors closed 2 years ago

frederikhors commented 3 years ago

I'm using the code from the docs: https://sveltequery.vercel.app/guides/placeholder-query-data#placeholder-data-from-cache:

const result = useQuery(["blogPost", blogPostId], () => fetch("/blogPosts"), {
  placeholderData: () => {
    // Use the smaller/preview version of the blogPost from the 'blogPosts' query as the placeholder data for this blogPost query
    return queryClient
      .getQueryData("blogPosts")
      ?.find((d) => d.id === blogPostId);
  },
});

My problem is I'm caching blogPosts along with pagination info, something like:

let pagination = { page: 1, limit: 20 };

$: posts = useQuery(
  ["blogPosts", pagination],
  async () => await fetchPosts({ pagination })
);

Is there a way I can use placeholderData with .getQueryData("blogPosts") key only (without pagination)?

frederikhors commented 3 years ago

I think there is an answer here: https://github.com/tannerlinsley/react-query/discussions/2336#discussioncomment-814165.

But I leave this open for a few days in case there is someone more experienced who wants to add something.

frederikhors commented 3 years ago

I was wrong. It doesn't work for list/detail with e.g. pagination.

The first query matched may not contain the post in the e.g. second page.

So I ended up with the below code, what do you think?

const result = useQuery(["blogPost", blogPostId], () => fetch("/blogPosts"), {
  placeholderData: () => {
    let postFound;

    useQueryClient()
      .getQueryCache()
      .findAll("blogPosts", { exact: false })
      .find((query) => {
        return query.state.data?.posts.find((p) => {
          if (p.id === postID) {
            postFound = p;
          }
        });
      });

    return postFound;
  },
});

Can you suggest something more elegant?

SomaticIT commented 2 years ago

This is not very elegant but as for now, this is the only way to make it work.

I think you can optimize a little by doing:

const result = useQuery(["blogPost", blogPostId], () => fetch("/blogPosts"), {
  placeholderData: () => {
    let postFound;

    useQueryClient()
      .getQueryCache()
      .findAll("blogPosts", { exact: false })
      .some((query) => {
        return query.state.data?.posts.some((p) => {
          if (p.id === postID) {
            postFound = p;
            return true;
          }
        });
      });

    return postFound;
  },
});

This will stop both loops when you data is found.

To help make it more elegant, I suggest writting an helper:

function findQueryData(queryClient, queryKey, finder) {
  let entityFound;

  const queries = queryClient.getQueryCache().findAll(queryKey, { exact: false });
  queries.some((query) => {
    if (query.stale.data) {
      const data = finder(query.stale.data);
      if (data) {
        entityFound = data;
        return true;
      }
    }
  });

  return entityFound;
}

Now you can:

const result = useQuery(["blogPost", blogPostId], () => fetch("/blogPosts"), {
  placeholderData: () => findQueryData(
    useQueryClient(),
    "blogPosts",
    (data) => data.posts.find((p) => p.id === postID)
  )
});