#How Do I Update The createAsync() Signal?

32 messages · Page 1 of 1 (latest)

devout thistle
#

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([]);

But when using createAsync() I only get access to a getter.

const notes = createAsync(() => getNotes());

How can I update the getter "notes" when using createAsync()?

prisma cove
#

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

A Solid TypeScript project based on @solidjs/router, solid-js, typescript, vite and vite-plugin-solid

GitHub

A universal router for Solid inspired by Ember and React Router - solidjs/solid-router

devout thistle
#

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

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 ...

devout thistle
#

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");

Call load() to take effect of loading the data on hover.

export const route = {
  load: () => getNotes(),
};

Create a reactive notes value with createAsync()

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. 
}

This seems like a pretty good solution. Would you agree?

prisma cove
# devout thistle Thank you. This seems a lot more involved than using createStore(). Two questi...

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

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...

prisma cove
devout thistle
#

Haha

prisma cove
#

How fast does supabase come back with updates?

devout thistle
#

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 () => {
prisma cove
#

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.

devout thistle
#

Wouldn't each call the fetch function initiate another network request to Supabase?

prisma cove
# devout thistle Wouldn't each call the fetch function initiate another network request to Supaba...

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

Basic Chat demonstration with server-sent events (SSE) - peerreynders/solid-start-sse-chat

GitHub

Basic Chat demonstration with server-sent events (SSE) - peerreynders/solid-start-sse-chat

devout thistle
#

Impressive work!

prisma cove
#

Seemed the right thing to do at the time.

fringe saddle
devout thistle
thorny forge
#

how do we handle error with createAsync ?

devout thistle
# thorny forge how do we handle error with createAsync ?

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");
// Calling createAsync()
const notes = createAsync(() => getNotes());
// Checking for the existence of notes()
<Show when={!notes()}>Could not load data</Show>
thorny forge
devout thistle
thorny forge
#

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

polar oar
thorny forge
prisma cove
# thorny forge I expected the createAsync to be createResouce with cache, but turns out they di...

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”.

thorny forge
#

i wonder why createAsync implementation does not return the resource tho, it return the data instead

prisma cove
#

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.