#useQuery Rerendering whenever new data is patched

12 messages · Page 1 of 1 (latest)

violet bolt
#

I have a rich text editor that is pushing data to my convexDB:

    const EditorComponent = () => {
        const updateContentDebounced = useCallback(_.debounce((content) => {
            updateContent({storageId: _storageId, content: JSON.stringify(content)})
        }, 5000), [_storageId, updateContent]);
    
        const editor: BlockNoteEditor = useBlockNote({
            initialContent: initialContent ? JSON.parse(initialContent) : undefined,
            onEditorContentChange: (editor) => {
                updateContentDebounced(editor.topLevelBlocks);
            },
        });
    
        return <BlockNoteView editor={editor} theme="light"/>;
    };```

The below is my updateContent:
```js
export const updateContent = mutation({
    args: {
        storageId: v.optional(v.union(v.null(), v.id("node"))),
        content: v.string(),
    },

    handler: async (ctx, args) => {
        if (args.storageId == null) {   
            return null;
        }
        const node = await ctx.db.patch(args.storageId, 
            {
                content: args.content,
            });
    },
})

The issue lies on each updateContent call, a mutation to the database is made and my front end component then seems to rerender, push a new line & unfocuses. Is this a behaviour of useQuery since it rerenders whenver a query result changes? Keen to hear if there are any ways to navigate this!

Thanks 🙂

full trellis
#
GitHub

Contribute to jamwt/convex-buffered-state development by creating an account on GitHub.

Reactivity has taken a dominant position today within web app development. Our components and app state are all reactive, and the world has adapted–mo...

violet bolt
#

Hi @full trellis you are pretty much spot on! Thanks so much for the above resources as well! Out of interest, why do you say that useBufferedState is more appropriate for my use case instead of useStableQuery? Not 100% sure what the differences are in their behaviour and why one may be more appropriate than the other 🫡

full trellis
#

useStableQuery is for when a query's arguments are changing, and you want to hold onto the old query result while the new one is calculating. It avoids the useQuery becoming undefined.

useBufferedState is for when a query is rerunning because of changing underlying data. It allows you to hold onto the old query result for as long as the client wants, avoiding rerendering new data until an explicit sync request.

In addition to the above, there may be different solutions where the rerendering does not unfocus the view. Some people have built collaborative text editing on Convex, where data is synced both ways without disrupting either editor. (I believe @flat matrix has an example. And cc @hidden basalt who has looked into this)

violet bolt
#

Thanks so much Lee, ur an absolute legend 🍑

#

@flat matrix would you mind sharing the example that you have please?

flat matrix
#

Gladly @violet bolt, you can find it here: https://github.com/rjdellecese/scroll. I'm not sure if the live site is working anymore as it's using a very old version of Convex, but the Convex code should still be pretty easy to follow. Check out the README for details and feel free to ask me any questions, if you have any!

jaunty aspen
#

Hello @flat matrix , Thanks so much for sharing the code, it is a very cool app! I am just trying to understand how you did autosave without blurring the text editor. I see the update function and am guessing this is called every time someone stops typing? But I can't seem to find when this function is called. Are you able to point me the right direction? Thank you!

flat matrix
#

Hey @jaunty aspen, sure thing!

The Tiptap editor is initialized with a parameter specifying that we should dispatch a message onTransaction—you can see that here (https://github.com/rjdellecese/scroll/blob/15f923738ecf0597a8bcd877d6af790b48bb2e5f/src/elm-ts/note.tsx#L583-L590).

So every time a transaction is applied to the editor (meaning every time the editor state has changed), this branch of the update function is executed: https://github.com/rjdellecese/scroll/blob/15f923738ecf0597a8bcd877d6af790b48bb2e5f/src/elm-ts/note.tsx#L338-L358. This does some debouncing (by checking areStepsInFlight) to check that we aren't currently running a mutation to save the latest changes (which are recorded as steps). If we aren't, it will check to see whether there are "sendable steps" (meaning, whether there are local changes that have not been persisted to the server yet), and send them if there are.

If you aren't familiar at all with how ProseMirror's collab module works, I strongly recommend reading https://prosemirror.net/docs/guide/#collab first. If you don't understand the algorithm that ProseMirror uses for collaborative editing (at least at a high level), you'll probably have trouble following the code in Scroll!

Once Convex releases its much-anticipated "components" feature, hopefully I'll be able to bundle this up more nicely for folks so that they don't need to understand as much to get rolling 🙂

brisk sonnet
#

Very glad I stumbled across this thread. I have a few questions:

  1. If we're using useCachedAuthQuery (custom auth query built on top of useCachedQuery) in places where we'd like behavior of useStableQuery and useBufferedState, should we still check the links shared before or are there any new helpers? e.g. something like makeUseStableQuery or something like that. This is what I have right now:
export function useCachedAuthStableQueryWithStatus<Query extends AuthFunction<"query">>(
  query: Query,
  ...args: AuthQueryArgsArray<Query>
): WithQueryStatus<FunctionReturnType<Query>> {
  const result = useCachedAuthQueryWithStatus(query, ...args);
  const stored = useRef(result);

  if (result.status !== "pending") {
    stored.current = result;
  }

  return stored.current;
}
  1. We're using optimistic updates in a bunch of places and noticed that components rerender when the server value is received. Will this only happen if the query returns a value that is not exactly the same as the optimistic update query (incl system fields)?
brisk sonnet
#

Any thoughts?