How Do I Update The createAsync() Signal?

I've been trying to use cache(), load(), and createAsync() because this pattern appears to be the preferred method for fetching data with SolidStart. Unfortunately, I can't figure out how to update the createAsync() signal. When using createStore() you get access to a getter and a setter:
const [ getNotes, setNotes ] = createStore([]);
const [ getNotes, setNotes ] = createStore([]);
But when using createAsync() I only get access to a getter.
const notes = createAsync(() => getNotes());
const notes = createAsync(() => getNotes());
How can I update the getter "notes" when using createAsync()?
27 Replies
peerreynders
peerreynders7mo ago
Warning this is probably a hack. I solved it at the cache level. I associated a synchronization data object with the cache. Update the synchronization data then kick off revalidation for the cache point and it pushes out the fresh data In my case it was easy because that was how the cache got its value but if you are blending fetching and mutating … you really should be doing optimistic updates with useSubmission. Example
peerreynders
StackBlitz
solid.js action cache interplay - StackBlitz
A Solid TypeScript project based on @solidjs/router, solid-js, typescript, vite and vite-plugin-solid
GitHub
GitHub - solidjs/solid-router: A universal router for Solid inspire...
A universal router for Solid inspired by Ember and React Router - solidjs/solid-router
ChrisThornham
ChrisThornhamOP7mo ago
Thank you. This seems a lot more involved than using createStore(). Two questions: 1. How much more performant is the cache method vs the store method? I'm wondering if it's worth all of the extra set up. 2. Ryan mentiond that we can use cache.set() to mutate data in this post :https://github.com/solidjs/solid-start/discussions/1225#discussioncomment-8806242 Could cache.set() work in this case?
GitHub
Mutate cache data · solidjs solid-start · Discussion #1225
Hi! Is there a pattern to optimistic mutate data loaded with cache and createAsync ? Similar to mutate in createResource. Would be great to be able to preload data on the server with use server in ...
ChrisThornham
ChrisThornhamOP7mo ago
I've got a different pattern working pretty well now. For the record, I'm doing all of this to use realtime updates from supabase. This pattern allows me to create a cache and load everything on the server while still taking advantage of realtime updates using a store. Here's how it works. Get the notes on the server
const getNotes = cache(async () => {
"use server";
// get the notes
}, "notes");
const getNotes = cache(async () => {
"use server";
// get the notes
}, "notes");
Call load() to take effect of loading the data on hover.
export const route = {
load: () => getNotes(),
};
export const route = {
load: () => getNotes(),
};
Create a reactive notes value with createAsync()
const notes = createAsync(() => getNotes());
const notes = createAsync(() => getNotes());
Pass notes to a child component <Notes notes={notes} /> Inside of <Notes /> load the data into a store and react to any realtime updates.
interface NoteType {
id: string;
note: string;
}

interface NotesProps {
notes: Accessor<any[] | undefined>;
}

export default function Notes(props: NotesProps) {
// Notes Store
const [notesStore, setNotesStore] = createStore<NoteType[]>([]);

createEffect(async () => {
// Populate the notes store
const notes = props.notes() ?? [];
setNotesStore(notes);

// Run realtime logic...
});

// use notesStore() to create inidividual notes.
}
interface NoteType {
id: string;
note: string;
}

interface NotesProps {
notes: Accessor<any[] | undefined>;
}

export default function Notes(props: NotesProps) {
// Notes Store
const [notesStore, setNotesStore] = createStore<NoteType[]>([]);

createEffect(async () => {
// Populate the notes store
const notes = props.notes() ?? [];
setNotesStore(notes);

// Run realtime logic...
});

// use notesStore() to create inidividual notes.
}
This seems like a pretty good solution. Would you agree?
peerreynders
peerreynders7mo ago
I'm wondering if it's worth all of the extra set up.
The elephant in the room is: what is the context you are using this in. Using cache.set() is flawed given that it destroys the value known to have been valid in the past which for all we know is how the server is still viewing the world. Start supports single flight mutations so that an action can immediately return the redirect result that would normally require a followup request by the browser to the URL listed in the location header of a 302 response-so if your connections are fast enough there is probably no need to leverage useSubmission. useSubmission exists to bridge that period of uncertainty where the client knows what the result should be but has not yet received agreement from the server-and it operates in a fashion that does not destroy values that are known to have been previously valid. Setters on signals and stores are great for local ephemeral state; i.e. state that would simply disappear (or be re-derived) once you actually reload the page from the server. But the cache/createAsync/useAction/useSubmission mechanism was created for non-ephemeral state; the type of things that both the server and the client have to agree on.
GitHub
Release v0.6.0 - Take Flight · solidjs/solid-start
Listening carefully to the feedback and reflecting on the second month of beta 2 we have prepared this release which improves on a lot of the usability around Start itself. This is less about featu...
peerreynders
peerreynders7mo ago
To be honest I know people do it but every time I see a setter inside a createEffect it sets my teeth on edge-to me it screams design issue. And I suspect the core of that is this:
use realtime updates from supabase.
ChrisThornham
ChrisThornhamOP7mo ago
Haha
peerreynders
peerreynders7mo ago
How fast does supabase come back with updates?
ChrisThornham
ChrisThornhamOP7mo ago
Very fast. Pretty much as soon as I click the button. Moving the setter outside of createEffect gives me an empty screen because notes hasn't populated when the componenet loads. That's why I placed it inside of createEffect().
const [notesStore, setNotesStore] = createStore<NoteType[]>([]);

// Populate the notes store
const notes = props.notes() ?? [];
setNotesStore(notes);

// create a variable to hold the realtime subscription
let subscription: RealtimeChannel;

createEffect(async () => {
const [notesStore, setNotesStore] = createStore<NoteType[]>([]);

// Populate the notes store
const notes = props.notes() ?? [];
setNotesStore(notes);

// create a variable to hold the realtime subscription
let subscription: RealtimeChannel;

createEffect(async () => {
peerreynders
peerreynders7mo ago
I'd redesign the cache handling. Have the update write to a synchronization area and then invalidate the cache so that the fetch function picks the up to date values.
ChrisThornham
ChrisThornhamOP7mo ago
Wouldn't each call the fetch function initiate another network request to Supabase?
peerreynders
peerreynders7mo ago
Example: SSE events show up whenever they feel like it. There is no "fetch". The most up-to-date state is held inside a simple (singleton) class: MessageHistory. HistoryState augments that class with reset and shunt functions which wrap cache invalidations. Within the context value that state and the cache points are bound together via their respective names. Now whenever a new set of messages comes in shunt is called: - MessageHistory is updated - Then the appropriate cache invalidations are triggered causing the cache points to read in the updated data from MessageHistory and pushing them onto the respective createAsync(Store).
GitHub
solid-start-sse-chat/src/components/history-context/index.tsx at d2...
Basic Chat demonstration with server-sent events (SSE) - peerreynders/solid-start-sse-chat
GitHub
solid-start-sse-chat/src/components/history-context/message-history...
Basic Chat demonstration with server-sent events (SSE) - peerreynders/solid-start-sse-chat
peerreynders
peerreynders7mo ago
So far nobody has responded to my query.
Discord
Discord - A New Way to Chat with Friends & Communities
Discord is the easiest way to communicate over voice, video, and text. Chat, hang out, and stay close with your friends and communities.
ChrisThornham
ChrisThornhamOP7mo ago
Impressive work!
peerreynders
peerreynders7mo ago
Seemed the right thing to do at the time.
brenelz
brenelz7mo ago
I accomplished this in a similar way as Chris here over for handling a websocket. It definitely feels kinda hacky https://github.com/brenelz/solid-poll/blob/main/src/routes/poll/%5Bid%5D.tsx#L23
GitHub
solid-poll/src/routes/poll/[id].tsx at main · brenelz/solid-poll
Contribute to brenelz/solid-poll development by creating an account on GitHub.
ChrisThornham
ChrisThornhamOP7mo ago
Yes, nearly identical. Thanks for weighing in @brenelz
TaQuanMinhLong
TaQuanMinhLong7mo ago
how do we handle error with createAsync ?
ChrisThornham
ChrisThornhamOP7mo ago
I think that depends on the code inside of your cache. If you are using fetch, I'd return any errors and handle them when calling createAsync(). I'm using Supabase. In my case, I check for the existence of notes() to handle errors.
// The cache
const getNotes = cache(async () => {
"use server";
const { data } = await supabase.from("notes").select();
if (!data) {
// Return if no data exists
return;
} else {
return data;
}
}, "notes");
// The cache
const getNotes = cache(async () => {
"use server";
const { data } = await supabase.from("notes").select();
if (!data) {
// Return if no data exists
return;
} else {
return data;
}
}, "notes");
// Calling createAsync()
const notes = createAsync(() => getNotes());
// Calling createAsync()
const notes = createAsync(() => getNotes());
// Checking for the existence of notes()
<Show when={!notes()}>Could not load data</Show>
// Checking for the existence of notes()
<Show when={!notes()}>Could not load data</Show>
TaQuanMinhLong
TaQuanMinhLong7mo ago
So we need to wrap it with the error boundary?
ChrisThornham
ChrisThornhamOP7mo ago
It’s an option, but I don’t think that’s needed. I’m not doing it here.
TaQuanMinhLong
TaQuanMinhLong7mo ago
If that is so, then when the error boundary catches the error, how would we have a button to retry ? It doesn't seem to work when using the revalidate function when the error caught It seems like the error boundary was no longer there after it caught the error Then when calling revalidate again it said uncaught errror
intelligent-worker-probe
maybe try: to wrap/extend your fetched data with hasError. So if hasError is true you can display the button to retry. I think caching is still a problem though, since the build-in caching mechanism does not know that an error has happened and caches it instead. So I guess you have to build your own caching mechanism?
TaQuanMinhLong
TaQuanMinhLong7mo ago
I expected the createAsync to be createResouce with cache, but turns out they didn't include the error state like what createResouce does
peerreynders
peerreynders7mo ago
they didn't include the error state like what createResouce does
From the docs
data.error tells you if the request has errored out; if so, it contains the error thrown by the fetcher. (Note: if you anticipate errors, you may want to wrap createResource in an ErrorBoundary.)
Sounds to me that the expectation is that you will pick up the error in the ErrorBoundary anyway. Another alternative is to just return the union (or tuple) of result and error from the cache fn- splitting state after the createAsync. Keep in mind SolidJS always focused on “primitives with which mechanisms can be built”.
TaQuanMinhLong
TaQuanMinhLong7mo ago
i wonder why createAsync implementation does not return the resource tho, it return the data instead
peerreynders
peerreynders7mo ago
The way I see it: createResource was Ryan's first crack at an async-to-sync-reactive primitive. Once he widened his scope to SSR and ultimately fullstack he realized that createResource wasn't actually a primitive. In terms of fullstack composability useAction/cache/createAsync were better building blocks. The fact that createResource appears inside of createAsync right now is temporary until SolidJS 2.0. At that point createAsync et al. will move into Solid core with implementations entirely decoupled from createResource.
peerreynders
peerreynders7mo ago
that aims to serve as stand-in for a future primitive we intend to bring to Solid core in 2.0.
https://github.com/solidjs/solid-router?tab=readme-ov-file#createasync
GitHub
GitHub - solidjs/solid-router: A universal router for Solid inspire...
A universal router for Solid inspired by Ember and React Router - solidjs/solid-router
Want results from more Discord servers?
Add your server