You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I am proposing a new primitive (or an enhancement to the upcoming resource API) that enables on-demand, async, memoized, and reactive client-side data fetching.
The goal is to bring the developer experience of SvelteKit’s remote functions (specifically the caching/sharing mechanism) to client-side logic, allowing shared state across components without the limitations of setContext or the complexity of manual cache invalidation.
Proposed API
The usage would look similar to a query remote function but designed for client-side logic (e.g., inside .svelte.js files).
Option A: String Key
import{online}from'svelte/reactivity/window';import{memoize}from'svelte/reactivity';// Hypothetical importexportconstgetTodos=(id)=>{returnmemoize(`/todos/${id}`,async()=>{// Track svelte signals:if(!online.current)returnnull;// Track signals after awaits (e.g. react to remote function refreshes)constuser=$derived(awaitgetUser());if(!user)returnnull;consttodos=fetch(`/api/todos/${id}?user=${user.id}`).then((res)=>res.json());// Since they have their own lifecycle, maybe even have an on/off notifier:$effect(()=>{// e.g. start websocket connectionreturn()=>{// e.g. cleanup websocket connection}});returntodos;});}
Currently, sharing async state in and outside the component tree is friction-heavy. While setContext / getContext is the standard solution for SSR-safe shared state, it imposes strict limitations in an async environment:
Component coupling: It must be called inside a component tree.
Async blocking: It cannot be called after an await expression.
Function boundary requirements: To maintain reactivity, state must be passed via getters (e.g., setContext('key', { get value() { return signal }})).
This proposal asks for a primitive that allows data to be defined globally (or in shared modules) and consumed locally, with the framework handling the lifecycle and cache sharing.
I have attempted to implement this pattern in userland, but I have hit specific roadblocks regarding how Svelte 5 handles reactivity across async boundaries.
1. The "Async Gap" in .svelte.js files
In Svelte components, $derived(await ...) magically handles signal tracking across await boundaries. However, replicating this inside standard .svelte.js functions is inconsistent.
Although using $derived inside a function technically compiles, the behavior is flaky. Reactivity often fails to track signals correctly if the execution involves complex async flows, or it requires multiple signal changes to "wake up" the tracking.
To memoize a resource that might be called outside a render effect (e.g., in an event handler), we need to create a root scope using $effect.root. However, $effect.root cannot contain async execution.
I am aware of the upcoming resource primitive in #16960. While exciting, the current design seems to rely exclusively on manual invalidation (.refresh()).
If I have a resource that depends on other resources, I have to manually cascade .refresh() calls. Ideally, the resource should track signals (including other resources) used in its "run" function and automatically re-run when dependencies change, keeping the value up to date without manual intervention.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
I am proposing a new primitive (or an enhancement to the upcoming
resourceAPI) that enables on-demand, async, memoized, and reactive client-side data fetching.The goal is to bring the developer experience of SvelteKit’s remote functions (specifically the caching/sharing mechanism) to client-side logic, allowing shared state across components without the limitations of
setContextor the complexity of manual cache invalidation.Proposed API
The usage would look similar to a
queryremote function but designed for client-side logic (e.g., inside.svelte.jsfiles).Option A: String Key
Option B: Schema/Auto-key (Remote function style)
Motivation
Currently, sharing async state in and outside the component tree is friction-heavy. While
setContext/getContextis the standard solution for SSR-safe shared state, it imposes strict limitations in anasyncenvironment:awaitexpression.setContext('key', { get value() { return signal }})).This proposal asks for a primitive that allows data to be defined globally (or in shared modules) and consumed locally, with the framework handling the lifecycle and cache sharing.
I have attempted to implement this pattern in userland, but I have hit specific roadblocks regarding how Svelte 5 handles reactivity across async boundaries.
1. The "Async Gap" in
.svelte.jsfilesIn Svelte components,
$derived(await ...)magically handles signal tracking across await boundaries. However, replicating this inside standard.svelte.jsfunctions is inconsistent.Although using
$derivedinside a function technically compiles, the behavior is flaky. Reactivity often fails to track signals correctly if the execution involves complex async flows, or it requires multiple signal changes to "wake up" the tracking.See reproduction in Playground
2.
$effect.rootdoes not support asyncTo memoize a resource that might be called outside a render effect (e.g., in an event handler), we need to create a root scope using
$effect.root. However,$effect.rootcannot contain async execution.I am aware of the upcoming
resourceprimitive in #16960. While exciting, the current design seems to rely exclusively on manual invalidation (.refresh()).If I have a resource that depends on other resources, I have to manually cascade
.refresh()calls. Ideally, the resource should track signals (including other resources) used in its "run" function and automatically re-run when dependencies change, keeping the value up to date without manual intervention.Beta Was this translation helpful? Give feedback.
All reactions