I have a tanstack query that displays drinks, and each drink is a link to a route that provides more information for the drink. The crappy part about adding tanstack is, when you navigate away from the page, going back to the page resets the query to the top. Is there any way to ensure the same yOffset or position when returning to the page?
Here is my code:
The +layout.ts
import { browser } from '$app/environment'
import { QueryClient } from '@tanstack/svelte-query'
import type { LayoutLoad } from './$types'
export const load: LayoutLoad = async () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
enabled: browser,
staleTime: 1000 * 60 * 5, // 5 Minutes
cacheTime: 1000 * 60 * 5, // 5 Minutes
},
},
})
return { queryClient }
}
the generated +page.svelte (the list of items)
<script lang="ts">
import { fade } from 'svelte/transition'
import { inview, type Options } from 'svelte-inview'
import { createInfiniteQuery, useQueryClient } from '@tanstack/svelte-query'
import { siteVars } from '../../sveltePageConfigVars'
import getOutputs from '../../utils/getOutputs'
let queryClient = useQueryClient()
let limit = 20
let loading = false
let noMoreResults = false
let searching = false
let searchText = ''
type GeneratedOutput = {
_id: string
user_id: string
image_url: string
date_created: string
date_modified: string
content: string
}
$: query = createInfiniteQuery({
queryKey: ['outputs', { type: 'list'}],
cacheTime: 1000 * 60 * 5,
queryFn: async ({pageParam}) => {
const res = await getOutputs(await pageParam, limit, searchText)
if (!res.length || res.length < limit) {
noMoreResults = true
}
return res
},
getNextPageParam: async (lastPage, allPages) => {
return lastPage.length * allPages.length
},
onSettled: () => {
loading = false
}
}) satisfies unknown
const options: Options = {
rootMargin: '50px'
}
$: fetchMore = async () => {
if (loading) return
loading = true
await $query.fetchNextPage()
}
$: reset = async () => {
noMoreResults = false
searching = true
queryClient.setQueryData(['outputs', { type: 'list'}], { pages: [ await getOutputs()] })
searching = false
}
$: search = async () => {
if (!searchText) return
noMoreResults = false
searching = true
queryClient.setQueryData(['outputs', { type: 'list'}], { pages: [ await getOutputs(0, undefined, searchText)] })
searching = false
}
</script>
<svelte:head>
<title>{siteVars.siteTitle} drink list</title>
<meta name="description" content="{siteVars.og_description}"/>
</svelte:head>
<form on:submit|preventDefault={search} class="flex justify-center w-full mt-8 btn-group">
<input bind:value={searchText} type="search" class=" relative input w-full max-w-md bg-bgDark rounded-l-lg rounded-r-none focus:outline-none focus:ring-0" placeholder="Enter your search here"/>
<button disabled={!searchText} type="submit" class="btn" on:click={search}>Search</button>
</form>
<div class="flex flex-col items-center justify-center items-center py-3 text-sm">
<p>You can <button on:click={reset} class="btn-link">reset</button> the results at any time.</p>
{#if searching}
<p class="py-3">Loading...</p>
{/if}
</div>
{#if $query.isSuccess }
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 mt-11">
{#each $query.data.pages as page}
{#each page as item, idx}
<a class="group" id={item._id} href={`/generated/${item._id}?from_generated=true`}>
<div class="relative card max-w-xl m-auto bg-zinc-900 aspect-square overflow-hidden group">
<div class="text-white z-10 absolute bottom-0 left-0 right-0 w-full opacity-0 backdrop-blur bg-[rgba(0,0,0,0.75)] p-3 font-semibold font-sans sm:text-lg text-3xl pt-3 transition-transform duration-100 translate-y-full group-hover:opacity-100 group-hover:translate-y-0">
{@html item.content?.match(/<h1.*>.*<\/h1>/g) ?? ''}
</div>
<img in:fade class="aspect-square group-hover:scale-105 transition-all duration-300 delay-50" src='{item.image_url}' alt="AI Generated drink" loading={idx > 3 ? 'lazy' : 'eager'} />
</div>
</a>
{/each}
{/each}
</div>
{#if !noMoreResults}
<div role="status" use:inview={options} on:inview_enter={fetchMore} class="p-6 my-6 w-full flex justify-center">
<svg aria-hidden="true" class="w-8 h-8 mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-primary" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
</svg>
<span class="sr-only">Loading...</span>
</div>
{/if}
{/if}
<div class="pb-6"></div>
the generated +page.ts (for the initial server response)
import type { PageLoad } from './$types'
import getOutputs from '../../utils/getOutputs'
const fetchData = async (fetch: (input: RequestInfo | URL, init?: RequestInit | undefined) => Promise<Response>, limit: number, offset: number) => {
try {
const response = await fetch(`${lambdaUrl}?limit=${limit}&offset=${offset}`, {
headers: {
'Content-Type': 'application/json'
}
})
const json = await response.json()
return { json }
} catch (e) {
console.log("Error: ", e)
return {
error: "There was an error with the request."
}
}
}
export const load: PageLoad = async ({ parent }) => {
const { queryClient } = await parent()
await queryClient.prefetchInfiniteQuery({
queryKey: ['outputs'],
queryFn: async () => await getOutputs(),
staleTime: 1000 * 60 * 5, // 5 Minutes
})
}
the [db-item] route:
<script lang="ts">
import { json } from '@sveltejs/kit';
import BackButton from '../../../components/BackButton.svelte';
import ShareBar from '../../../components/ShareBar.svelte';
import { facebookAppID, siteVars, socialHandles } from '../../../sveltePageConfigVars.js'
type GeneratedOutput = {
_id: string
user_id: string
image_url: string
date_created: string
date_modified: string
content: string,
from_generated?: boolean
}
export let data: { json: GeneratedOutput[]}
let { json: responseData} = data
let { _id, from_generated } = responseData[0]
let drinkTitle = responseData[0]?.content.match(/<h1.*>(.*)<\/h1>/) ?? ''
let drinkContent = responseData[0]?.content.match(/<div class="content".*>(.*)<\/div>/) ?? ''
</script>
<svelte:head>
<!-- Facebook -->
<meta property="og:type" content="website" />
<meta property="og:title" content={`${!!drinkTitle && drinkTitle.length > 1 ? drinkTitle[1].replaceAll(/^[ \t]+|[ \t]+$/g, '') : ''}`} />
<meta property="og:image" content={responseData[0]?.image_url} />
<meta property="og:url" content="{siteVars.siteDomain}/generated/{responseData[0]?._id}" />
<meta property="og:description" content={siteVars.og_description} />
<meta property="fb:app_id" content="{facebookAppID}" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={`${!!drinkTitle && drinkTitle.length > 1 ? drinkTitle[1].replaceAll(/^[ \t]+|[ \t]+$/g, '') : ''}`} />
<meta name="twitter:description" content="Drink from {siteVars.siteDomain}" />
<meta name="twitter:creator" content="{socialHandles.twitter}" />
<meta name="twitter:image" content="{Object.values(responseData)[0]?.image_url}" />
<meta name="twitter:domain" content="{siteVars.siteDomain}" />
<!-- Permalink & Title -->
<link rel="canonical" href="{siteVars.siteDomain}/generated/{responseData[0]?._id}" />
<title>{siteVars.siteTitle + " creation: " + drinkTitle[1]}</title>
<meta name="description" content="{drinkTitle[1] && drinkContent[1] ? `${drinkTitle[1]} -- ${drinkContent[1].slice(150)}` : siteVars.og_description}" />
</svelte:head>
{#await responseData}
<p>loading...</p>
{:then responseData}
<BackButton --text-color="#000" id={_id} from_generated={from_generated}/>
{#if !!!Object.values(responseData)[0]?.content}
<div class="no-return">
<h1>No Result</h1>
<p>There doesn't seem to be anything here. Click 'back' to go back...</p>
</div>
{:else}
{#each Object.values(responseData) as item}
<div class="container">
<img class="drink-img" src={item.image_url} alt="AI Generated Drink"/>
<div class="content">
{@html item.content}
</div>
<ShareBar id={item._id} image_url={responseData[0]?.image_url} title={!!drinkTitle && drinkTitle.length > 1 ? drinkTitle[1].replaceAll(/^[ \t]+|[ \t]+$/g, '') : ''} text={item?.content} />
</div>
{/each}
{/if}
{:catch}
<p>Nothing was returned</p>
{/await}
<style lang="scss">
.container {
padding-bottom: 3rem;
width: 100%;
}
.content {
padding: 10px;
width: 100%;
max-width: 800px;
margin: auto;
}
.drink-img {
display: block;
width: 100%;
max-width: 600px;
margin: auto;
height: auto;
}
.no-return {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: auto;
max-width: 600px;
min-height: 50vh;
& h1 {
align-self: flex-start;
}
}
</style>