import React from "react"; import { useC2C } from "../../../hooks/useC2C"; import Cursor from "./Cursor"; export const Cursors = ({ users }) => { const [c2c] = useC2C(); const [cursors, setCursors] = React.useState({}); const preventRef = React.useRef(false); const usersById = React.useMemo(() => { return users.reduce((acc, user) => { acc[user.id] = user; return acc; }, {}); }, [users]); // Prevent race condition when removing user const currentCursor = React.useMemo(() => { return users.reduce((acc, user) => { if (cursors[user.id]) { acc[user.id] = cursors[user.id]; } return acc; }, {}); }, [users, cursors]); React.useEffect(() => { setCursors((prevCursors) => { return users.reduce((acc, user) => { if (prevCursors[user.id]) { acc[user.id] = prevCursors[user.id]; } return acc; }, {}); }); }, [users]); React.useEffect(() => { const unsub = []; unsub.push( c2c.subscribe("cursorMove", ({ userId, pos }) => { // Avoid move after cursor off if (preventRef.current) return; setCursors((prevCursors) => { return { ...prevCursors, [userId]: pos, }; }); }) ); unsub.push( c2c.subscribe("cursorOff", ({ userId }) => { setCursors((prevCursors) => { const newCursors = { ...prevCursors, }; delete newCursors[userId]; return newCursors; }); // Prevent next moves preventRef.current = true; setTimeout(() => { preventRef.current = false; }, 100); }) ); return () => { unsub.map((c) => c()); }; }, [c2c]); return (
{Object.entries(currentCursor).map(([userId, pos]) => ( ))}
); }; export default Cursors;