Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue when using with tiptap collaboration cursor #7

Open
seleckis opened this issue Jan 5, 2024 · 2 comments
Open

Issue when using with tiptap collaboration cursor #7

seleckis opened this issue Jan 5, 2024 · 2 comments

Comments

@seleckis
Copy link

seleckis commented Jan 5, 2024

useSelf works well in most situations. But when I use it with TipTap editor and CollaborationCursor extension here comes an issue:

In a component that renders tipTap editor I need to update the presence of the user like this:

const self = useSelf();
useEffect(() => {
  if (!self || !editor) return;
  editor.commands.updateUser({
    name: self.username,
    color: self.color,
  });
}, [self, editor]);

Unfortunately this code makes tiptap editor opened websocket to go mad and infinity send a huge amount of messages.

I have create this hook:

import { isEqual } from "lodash-es";
import { useSelf as usePresenceSelf } from "y-presence";

export const useSelf = () => {
  const self = usePresenceSelf(awareness) as YUser;
  const selfRef = useRef(self);
  useEffect(() => {
    if (isEqual(self, selfRef.current)) return;
    selfRef.current = self;
  }, [self]);
  return selfRef.current;
};

Now update is working as expected and there is no infinite loop of messages anymore.

Should this be added in the y-presence lib? What do you think?

@nimeshnayaju
Copy link
Owner

In a component that renders tipTap editor I need to update the presence of the user like this:

const self = useSelf();
useEffect(() => {
  if (!self || !editor) return;
  editor.commands.updateUser({
    name: self.username,
    color: self.color,
  });
}, [self, editor]);

Unfortunately this code makes tiptap editor opened websocket to go mad and infinity send a huge amount of messages.

Hi @seleckis, in this code snippet you posted, are you attempting to update the user presence every time the value of self is updated? If so, the behaviour you experienced is expected. It's almost like updating the current user's presence every time the user's presence is updated, which triggers an infinite loop of updating presence. If not, do you mind providing more context on what you are trying to achieve with the code snippet? I'd love to help if I can.

I have create this hook:

import { isEqual } from "lodash-es";
import { useSelf as usePresenceSelf } from "y-presence";

export const useSelf = () => {
  const self = usePresenceSelf(awareness) as YUser;
  const selfRef = useRef(self);
  useEffect(() => {
    if (isEqual(self, selfRef.current)) return;
    selfRef.current = self;
  }, [self]);
  return selfRef.current;
};

This is a nice way to ensure selfRef is only updated if the current user's presence is actually updated. But, there are some minor problems with this approach from what I understand. First of all, React doesn't recommend reading ref.current during rendering (Link) and second, if your goal is to perform some work when user's presence is updated, I'd recommend subscribing (and then unsubscribing during unmount) to user's awareness using the awaresness.on('update') or awareness.on('change') API (Link).

@seleckis
Copy link
Author

seleckis commented Jan 5, 2024

Well, I need to set user data for CollaborationCursor extension. According to documentation it should be set when initializing editor with extensions:

const self = useSelf();
const extensions = useMemo(() => [
  Collaboration.configure({
    document: ydoc,
    fragment: yField,
  }),
  CollaborationCursor.configure({
    provider: yprovider,
    user: {
      user: self?.username,
      color: self?.color,
    },
  }),
], [yField, self]);
const editor = useEditor({
  extensions,
});

but in this case sometimes self?.username returns clientID of current user and does not update user data when useSelf from y-presence updates and returns correct user data. That is why I'm using useEffect to update user data for CollaborationCursor.

I have broken down the object useSelf gives and discovered that CollaborationCursor sets its own data, maybe that is why useSelf returns a new updated object every time.

So do you suggest to use awareness events for this case and not useSelf? Alright:

useEffect(() => {
  const onChange = ({ added, updated, removed }) => {
    console.log(added, updated, removed);
  })
  awareness.on("change", onChange);
  return () => {
    awareness.off("change", onChange);
  };
}, []);

in this case it gives me only clientID but not other data like username and color.

Alright, I've ended up with this solution:

export const useAwareness = (cb: (self: YUser | null) => void) => {
  useEffect(() => {
    const onChange = ({ updated }: { updated: number[] }) => {
      if (updated.includes(awareness.clientID)) {
        const self = awareness.getLocalState();
        cb(self as YUser);
      }
    };
    awareness.on("change", onChange);
    return () => {
      awareness.off("change", onChange);
    };
  }, [cb]);
};
...
useAwareness((self) => {
  if (!editor || !self?.username) return;

  const { username, color } = self;

  editor.commands.updateUser({
    name: username,
    color,
  });
});

This works well, updates tiptap collaboration cursor with correct data and does not force websocket to send infinite messages. I was just hoping y-presence lib could do these things, but maybe I'm wrong.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants