Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork4.6k
Description
Describe the problem
While building apps with Svelte 5, I’ve often run into the need to share and reuse the state of async requests (data, loading, error) across multiple components — similar to how React Query or SWR work in React.
As far as I know, there isn’t an official or idiomatic pattern in Svelte for implementing a global request cache. This often leads developers (myself included) to reinvent the wheel, usually borrowing patterns from React that don’t feel quite natural in Svelte.
I’m aware of TanStack Query for Svelte, but it seems to have several issues with Svelte 5 and doesn’t appear to be prioritized at the moment.
Below is my current approach. I suspect I’ve leaned too heavily on React-style patterns and that it is not idiomatic Svelte 5. I’m still very new to Svelte 5, so it’s possible I’m overcomplicating things — and I’d appreciate any guidance...
📐 Current Approach (Most likely wrong / an anti-pattern)
I maintain a global store:
typeGlobalRequestCacheStore=SvelteMap<string,RequestCache<StableKey,unknown>>;letglobalRequestCacheStore:GlobalRequestCacheStore|null=null;
It should be initialized at the root of the app with:
constglobalRequestCache=()=>{if(globalRequestCacheStore!=null){thrownewError('globalRequestCacheStore already exists');}globalRequestCacheStore=newSvelteMap();return()=>{globalRequestCacheStore=null;};};
The root component calls like this:
constdestroyCache=globalRequestCache();onDestroy(()=>destroyCache());
The store holds instances of class given a corresponding hash of the key.
(Below is a simplified version of the class implementation)
classRequestCache<KextendsStableKey,T>{ #state=$state<{data:T|null;error:Error|null;status:RequestStatus}>({data:null,error:null,status:'idle'}); #requestFn:RequestFn<K,T>; #key:K; #hash:string;constructor(key:K,requestFn:RequestFn<K,T>){this.#key=key;this.#requestFn=requestFn;this.#hash=stableHash(key);this.request();}mutate=(fn:(current:T|null)=>T|null)=>{this.#state.data=fn(this.#state.data);};request=():Promise<T|null>=>{this.#state={data:null,error:null,status:'loading'};returnthis.#requestFn(this.#key).then((r)=>{this.#state={data:r,error:null,status:'completed'};returnr;}).catch((e)=>{this.#state={data:null,error:e,status:'errored'};returnnull;});};getstate(){returnthis.#state;}gethash(){returnthis.#hash;}}
Components can access a cached request via:
constgetRequestCache=<KextendsStableKey,T>(key:K,requestFn:RequestFn<K,T>,):RequestCache<K,T>=>{consthash=stableHash(key);if(globalRequestCacheStore==null){thrownewError('globalRequestCacheStore not initialized');}constexisting=globalRequestCacheStore.get(hash);if(existing){returnexistingasRequestCache<K,T>;}constinstance=newRequestCache<K,T>(key,requestFn);globalRequestCacheStore.set(hash,instance);returninstance;};
Which you can wrap like:
exportconstgetNote=(token:string,id:number)=>{// getRequestCache(key, fetcher)returngetRequestCache({url:`http://api.com/note/${id}`, token},async({ url, token})=>{awaitsleep(2000);return{ url, token,note:'I am a note!'};});};
And then in a child component:
const{ id}=$props();constrequestInstance=getNote('my-token',id);// requestInstance.state.status === "loading"
⚠️ The Problem
Whenid
changes (via props), thegetNote
call is still referring to the cached instance for the oldid
, because the component doesn't callgetNote
again. You can wrap it with$derive
because it creates an object and stores it ($derive
must not include unsafe mutations or side effects)
My workaround for now is to force Svelte to recreate the child by wrapping it in a#key
block:
{#keyid} <Noteid={id} />{/key}
This works, but feels awkward — perhaps even an anti-pattern.
Describe the proposed solution
It would be nice the Svelte team provide an official, idiomatic recommendation for implementing aglobal request cache in Svelte, similar in spirit to howReact Query orSWR work in React.
This would make it easy to:
✅Easily share and reuse async request state (data
,loading
,error
) across multiple components.
✅Mutate and revalidate cached data from anywhere in the app, enabling patterns like optimistic updates, rollbacks on error, manual refresh actions, or keeping related views (e.g., a list of notes) in sync when items are created, updated, or deleted.
✅Ensure cached data remains consistent and reactive throughout the app, reflecting the latest state without manual intervention.
A recommended pattern, utility, or built-in API to support this common use case would be very helpful.
Importance
would make my life easier