[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6X76QDS3VB7Y6YGT2C094TA":3,"skill-same:01M6X76QDS3VB7Y6YGT2C094TA":77},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":55,"commitSha":56,"skillDirectory":57,"forksCount":58,"repositoryHtmlUrl":59,"repositoryHomepageUrl":6,"ownerHtmlUrl":60,"ownerBlogUrl":61,"ownerDescription":62,"updatedAt":63,"install":64,"resources":76},"01M6X76QDS3VB7Y6YGT2C094TA","convex-realtime",null,"Patterns for building reactive apps including subscription management, optimistic updates, cache behavior, and paginated queries with cursor-based loading","Cheggin\u002Frequest-for-startups","Cheggin","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F79483996?v=4","01M6X6MGT5512GJHE8DY5B1ZQJ","Reagan Hsu","01M6X6MGY03X7ZJNVRFM3R1040",1,"GPL-3.0","https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fconvex-realtime\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"realtime","convex","convex realtime","reactive","for","updates","true","with","loading","apps","build","pagination","optimistic-updates","subscriptions","queries","functions","cursor","behavior","check","cache","2026-11-10 10:12:30",false,"---\nname: convex-realtime\ndisplayName: Convex Realtime\ndescription: Patterns for building reactive apps including subscription management, optimistic updates, cache behavior, and paginated queries with cursor-based loading\nuser-invocable: true\nversion: 1.0.0\nauthor: Convex\ntags: [convex, realtime, subscriptions, optimistic-updates, pagination]\ngroup: build\nprerequisites: [convex-functions]\nnext: [convex-security-check]\nworkflows: [full-startup]\n---\n\n# Convex Realtime\n\nBuild reactive applications with Convex's real-time subscriptions, optimistic updates, intelligent caching, and cursor-based pagination.\n\n## Documentation Sources\n\nBefore implementing, do not assume; fetch the latest documentation:\n\n- Primary: https:\u002F\u002Fdocs.convex.dev\u002Fclient\u002Freact\n- Optimistic Updates: https:\u002F\u002Fdocs.convex.dev\u002Fclient\u002Freact\u002Foptimistic-updates\n- Pagination: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Fpagination\n- For broader context: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n\n## Instructions\n\n### How Convex Realtime Works\n\n1. **Automatic Subscriptions** - useQuery creates a subscription that updates automatically\n2. **Smart Caching** - Query results are cached and shared across components\n3. **Consistency** - All subscriptions see a consistent view of the database\n4. **Efficient Updates** - Only re-renders when relevant data changes\n\n### Basic Subscriptions\n\n```typescript\n\u002F\u002F React component with real-time data\nimport { useQuery } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction TaskList({ userId }: { userId: Id\u003C\"users\"> }) {\n  \u002F\u002F Automatically subscribes and updates in real-time\n  const tasks = useQuery(api.tasks.list, { userId });\n\n  if (tasks === undefined) {\n    return \u003Cdiv>Loading...\u003C\u002Fdiv>;\n  }\n\n  return (\n    \u003Cul>\n      {tasks.map((task) => (\n        \u003Cli key={task._id}>{task.title}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  );\n}\n```\n\n### Conditional Queries\n\n```typescript\nimport { useQuery } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction UserProfile({ userId }: { userId: Id\u003C\"users\"> | null }) {\n  \u002F\u002F Skip query when userId is null\n  const user = useQuery(\n    api.users.get,\n    userId ? { userId } : \"skip\"\n  );\n\n  if (userId === null) {\n    return \u003Cdiv>Select a user\u003C\u002Fdiv>;\n  }\n\n  if (user === undefined) {\n    return \u003Cdiv>Loading...\u003C\u002Fdiv>;\n  }\n\n  return \u003Cdiv>{user.name}\u003C\u002Fdiv>;\n}\n```\n\n### Mutations with Real-time Updates\n\n```typescript\nimport { useMutation, useQuery } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction TaskManager({ userId }: { userId: Id\u003C\"users\"> }) {\n  const tasks = useQuery(api.tasks.list, { userId });\n  const createTask = useMutation(api.tasks.create);\n  const toggleTask = useMutation(api.tasks.toggle);\n\n  const handleCreate = async (title: string) => {\n    \u002F\u002F Mutation triggers automatic re-render when data changes\n    await createTask({ title, userId });\n  };\n\n  const handleToggle = async (taskId: Id\u003C\"tasks\">) => {\n    await toggleTask({ taskId });\n  };\n\n  return (\n    \u003Cdiv>\n      \u003Cbutton onClick={() => handleCreate(\"New Task\")}>Add Task\u003C\u002Fbutton>\n      \u003Cul>\n        {tasks?.map((task) => (\n          \u003Cli key={task._id} onClick={() => handleToggle(task._id)}>\n            {task.completed ? \"✓\" : \"○\"} {task.title}\n          \u003C\u002Fli>\n        ))}\n      \u003C\u002Ful>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### Optimistic Updates\n\nShow changes immediately before server confirmation:\n\n```typescript\nimport { useMutation, useQuery } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\nimport { Id } from \"..\u002Fconvex\u002F_generated\u002FdataModel\";\n\nfunction TaskItem({ task }: { task: Task }) {\n  const toggleTask = useMutation(api.tasks.toggle).withOptimisticUpdate(\n    (localStore, args) => {\n      const { taskId } = args;\n      const currentValue = localStore.getQuery(api.tasks.get, { taskId });\n      \n      if (currentValue !== undefined) {\n        localStore.setQuery(api.tasks.get, { taskId }, {\n          ...currentValue,\n          completed: !currentValue.completed,\n        });\n      }\n    }\n  );\n\n  return (\n    \u003Cdiv onClick={() => toggleTask({ taskId: task._id })}>\n      {task.completed ? \"✓\" : \"○\"} {task.title}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### Optimistic Updates for Lists\n\n```typescript\nimport { useMutation } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction useCreateTask(userId: Id\u003C\"users\">) {\n  return useMutation(api.tasks.create).withOptimisticUpdate(\n    (localStore, args) => {\n      const { title, userId } = args;\n      const currentTasks = localStore.getQuery(api.tasks.list, { userId });\n      \n      if (currentTasks !== undefined) {\n        \u002F\u002F Add optimistic task to the list\n        const optimisticTask = {\n          _id: crypto.randomUUID() as Id\u003C\"tasks\">,\n          _creationTime: Date.now(),\n          title,\n          userId,\n          completed: false,\n        };\n        \n        localStore.setQuery(api.tasks.list, { userId }, [\n          optimisticTask,\n          ...currentTasks,\n        ]);\n      }\n    }\n  );\n}\n```\n\n### Cursor-Based Pagination\n\n```typescript\n\u002F\u002F convex\u002Fmessages.ts\nimport { query } from \".\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\nimport { paginationOptsValidator } from \"convex\u002Fserver\";\n\nexport const listPaginated = query({\n  args: {\n    channelId: v.id(\"channels\"),\n    paginationOpts: paginationOptsValidator,\n  },\n  handler: async (ctx, args) => {\n    return await ctx.db\n      .query(\"messages\")\n      .withIndex(\"by_channel\", (q) => q.eq(\"channelId\", args.channelId))\n      .order(\"desc\")\n      .paginate(args.paginationOpts);\n  },\n});\n```\n\n```typescript\n\u002F\u002F React component with pagination\nimport { usePaginatedQuery } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction MessageList({ channelId }: { channelId: Id\u003C\"channels\"> }) {\n  const { results, status, loadMore } = usePaginatedQuery(\n    api.messages.listPaginated,\n    { channelId },\n    { initialNumItems: 20 }\n  );\n\n  return (\n    \u003Cdiv>\n      {results.map((message) => (\n        \u003Cdiv key={message._id}>{message.content}\u003C\u002Fdiv>\n      ))}\n      \n      {status === \"CanLoadMore\" && (\n        \u003Cbutton onClick={() => loadMore(20)}>Load More\u003C\u002Fbutton>\n      )}\n      \n      {status === \"LoadingMore\" && \u003Cdiv>Loading...\u003C\u002Fdiv>}\n      \n      {status === \"Exhausted\" && \u003Cdiv>No more messages\u003C\u002Fdiv>}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### Infinite Scroll Pattern\n\n```typescript\nimport { usePaginatedQuery } from \"convex\u002Freact\";\nimport { useEffect, useRef } from \"react\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction InfiniteMessageList({ channelId }: { channelId: Id\u003C\"channels\"> }) {\n  const { results, status, loadMore } = usePaginatedQuery(\n    api.messages.listPaginated,\n    { channelId },\n    { initialNumItems: 20 }\n  );\n  \n  const observerRef = useRef\u003CIntersectionObserver>();\n  const loadMoreRef = useRef\u003CHTMLDivElement>(null);\n\n  useEffect(() => {\n    if (observerRef.current) {\n      observerRef.current.disconnect();\n    }\n\n    observerRef.current = new IntersectionObserver((entries) => {\n      if (entries[0].isIntersecting && status === \"CanLoadMore\") {\n        loadMore(20);\n      }\n    });\n\n    if (loadMoreRef.current) {\n      observerRef.current.observe(loadMoreRef.current);\n    }\n\n    return () => observerRef.current?.disconnect();\n  }, [status, loadMore]);\n\n  return (\n    \u003Cdiv>\n      {results.map((message) => (\n        \u003Cdiv key={message._id}>{message.content}\u003C\u002Fdiv>\n      ))}\n      \u003Cdiv ref={loadMoreRef} style={{ height: 1 }} \u002F>\n      {status === \"LoadingMore\" && \u003Cdiv>Loading...\u003C\u002Fdiv>}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### Multiple Subscriptions\n\n```typescript\nimport { useQuery } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction Dashboard({ userId }: { userId: Id\u003C\"users\"> }) {\n  \u002F\u002F Multiple subscriptions update independently\n  const user = useQuery(api.users.get, { userId });\n  const tasks = useQuery(api.tasks.list, { userId });\n  const notifications = useQuery(api.notifications.unread, { userId });\n\n  const isLoading = user === undefined || \n                    tasks === undefined || \n                    notifications === undefined;\n\n  if (isLoading) {\n    return \u003Cdiv>Loading...\u003C\u002Fdiv>;\n  }\n\n  return (\n    \u003Cdiv>\n      \u003Ch1>Welcome, {user.name}\u003C\u002Fh1>\n      \u003Cp>You have {tasks.length} tasks\u003C\u002Fp>\n      \u003Cp>{notifications.length} unread notifications\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## Examples\n\n### Real-time Chat Application\n\n```typescript\n\u002F\u002F convex\u002Fmessages.ts\nimport { query, mutation } from \".\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\nexport const list = query({\n  args: { channelId: v.id(\"channels\") },\n  returns: v.array(v.object({\n    _id: v.id(\"messages\"),\n    _creationTime: v.number(),\n    content: v.string(),\n    authorId: v.id(\"users\"),\n    authorName: v.string(),\n  })),\n  handler: async (ctx, args) => {\n    const messages = await ctx.db\n      .query(\"messages\")\n      .withIndex(\"by_channel\", (q) => q.eq(\"channelId\", args.channelId))\n      .order(\"desc\")\n      .take(100);\n\n    \u002F\u002F Enrich with author names\n    return Promise.all(\n      messages.map(async (msg) => {\n        const author = await ctx.db.get(msg.authorId);\n        return {\n          ...msg,\n          authorName: author?.name ?? \"Unknown\",\n        };\n      })\n    );\n  },\n});\n\nexport const send = mutation({\n  args: {\n    channelId: v.id(\"channels\"),\n    authorId: v.id(\"users\"),\n    content: v.string(),\n  },\n  returns: v.id(\"messages\"),\n  handler: async (ctx, args) => {\n    return await ctx.db.insert(\"messages\", {\n      channelId: args.channelId,\n      authorId: args.authorId,\n      content: args.content,\n    });\n  },\n});\n```\n\n```typescript\n\u002F\u002F ChatRoom.tsx\nimport { useQuery, useMutation } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\nimport { useState, useRef, useEffect } from \"react\";\n\nfunction ChatRoom({ channelId, userId }: Props) {\n  const messages = useQuery(api.messages.list, { channelId });\n  const sendMessage = useMutation(api.messages.send);\n  const [input, setInput] = useState(\"\");\n  const messagesEndRef = useRef\u003CHTMLDivElement>(null);\n\n  \u002F\u002F Auto-scroll to bottom on new messages\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const handleSend = async (e: React.FormEvent) => {\n    e.preventDefault();\n    if (!input.trim()) return;\n\n    await sendMessage({\n      channelId,\n      authorId: userId,\n      content: input.trim(),\n    });\n    setInput(\"\");\n  };\n\n  return (\n    \u003Cdiv className=\"chat-room\">\n      \u003Cdiv className=\"messages\">\n        {messages?.map((msg) => (\n          \u003Cdiv key={msg._id} className=\"message\">\n            \u003Cstrong>{msg.authorName}:\u003C\u002Fstrong> {msg.content}\n          \u003C\u002Fdiv>\n        ))}\n        \u003Cdiv ref={messagesEndRef} \u002F>\n      \u003C\u002Fdiv>\n      \n      \u003Cform onSubmit={handleSend}>\n        \u003Cinput\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type a message...\"\n        \u002F>\n        \u003Cbutton type=\"submit\">Send\u003C\u002Fbutton>\n      \u003C\u002Fform>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## Best Practices\n\n- Never run `npx convex deploy` unless explicitly instructed\n- Never run any git commands unless explicitly instructed\n- Use \"skip\" for conditional queries instead of conditionally calling hooks\n- Implement optimistic updates for better perceived performance\n- Use usePaginatedQuery for large datasets\n- Handle undefined state (loading) explicitly\n- Avoid unnecessary re-renders by memoizing derived data\n\n## Common Pitfalls\n\n1. **Conditional hook calls** - Use \"skip\" instead of if statements\n2. **Not handling loading state** - Always check for undefined\n3. **Missing optimistic update rollback** - Optimistic updates auto-rollback on error\n4. **Over-fetching with pagination** - Use appropriate page sizes\n5. **Ignoring subscription cleanup** - React handles this automatically\n\n## References\n\n- Convex Documentation: https:\u002F\u002Fdocs.convex.dev\u002F\n- Convex LLMs.txt: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n- React Client: https:\u002F\u002Fdocs.convex.dev\u002Fclient\u002Freact\n- Optimistic Updates: https:\u002F\u002Fdocs.convex.dev\u002Fclient\u002Freact\u002Foptimistic-updates\n- Pagination: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Fpagination\n","# Convex Realtime\n\nBuild reactive applications with Convex's real-time subscriptions, optimistic updates, intelligent caching, and cursor-based pagination.\n\n## Documentation Sources\n\nBefore implementing, do not assume; fetch the latest documentation:\n\n- Primary: https:\u002F\u002Fdocs.convex.dev\u002Fclient\u002Freact\n- Optimistic Updates: https:\u002F\u002Fdocs.convex.dev\u002Fclient\u002Freact\u002Foptimistic-updates\n- Pagination: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Fpagination\n- For broader context: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n\n## Instructions\n\n### How Convex Realtime Works\n\n1. **Automatic Subscriptions** - useQuery creates a subscription that updates automatically\n2. **Smart Caching** - Query results are cached and shared across components\n3. **Consistency** - All subscriptions see a consistent view of the database\n4. **Efficient Updates** - Only re-renders when relevant data changes\n\n### Basic Subscriptions\n\n```typescript\n\u002F\u002F React component with real-time data\nimport { useQuery } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction TaskList({ userId }: { userId: Id\u003C\"users\"> }) {\n  \u002F\u002F Automatically subscribes and updates in real-time\n  const tasks = useQuery(api.tasks.list, { userId });\n\n  if (tasks === undefined) {\n    return \u003Cdiv>Loading...\u003C\u002Fdiv>;\n  }\n\n  return (\n    \u003Cul>\n      {tasks.map((task) => (\n        \u003Cli key={task._id}>{task.title}\u003C\u002Fli>\n      ))}\n    \u003C\u002Ful>\n  );\n}\n```\n\n### Conditional Queries\n\n```typescript\nimport { useQuery } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction UserProfile({ userId }: { userId: Id\u003C\"users\"> | null }) {\n  \u002F\u002F Skip query when userId is null\n  const user = useQuery(\n    api.users.get,\n    userId ? { userId } : \"skip\"\n  );\n\n  if (userId === null) {\n    return \u003Cdiv>Select a user\u003C\u002Fdiv>;\n  }\n\n  if (user === undefined) {\n    return \u003Cdiv>Loading...\u003C\u002Fdiv>;\n  }\n\n  return \u003Cdiv>{user.name}\u003C\u002Fdiv>;\n}\n```\n\n### Mutations with Real-time Updates\n\n```typescript\nimport { useMutation, useQuery } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction TaskManager({ userId }: { userId: Id\u003C\"users\"> }) {\n  const tasks = useQuery(api.tasks.list, { userId });\n  const createTask = useMutation(api.tasks.create);\n  const toggleTask = useMutation(api.tasks.toggle);\n\n  const handleCreate = async (title: string) => {\n    \u002F\u002F Mutation triggers automatic re-render when data changes\n    await createTask({ title, userId });\n  };\n\n  const handleToggle = async (taskId: Id\u003C\"tasks\">) => {\n    await toggleTask({ taskId });\n  };\n\n  return (\n    \u003Cdiv>\n      \u003Cbutton onClick={() => handleCreate(\"New Task\")}>Add Task\u003C\u002Fbutton>\n      \u003Cul>\n        {tasks?.map((task) => (\n          \u003Cli key={task._id} onClick={() => handleToggle(task._id)}>\n            {task.completed ? \"✓\" : \"○\"} {task.title}\n          \u003C\u002Fli>\n        ))}\n      \u003C\u002Ful>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### Optimistic Updates\n\nShow changes immediately before server confirmation:\n\n```typescript\nimport { useMutation, useQuery } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\nimport { Id } from \"..\u002Fconvex\u002F_generated\u002FdataModel\";\n\nfunction TaskItem({ task }: { task: Task }) {\n  const toggleTask = useMutation(api.tasks.toggle).withOptimisticUpdate(\n    (localStore, args) => {\n      const { taskId } = args;\n      const currentValue = localStore.getQuery(api.tasks.get, { taskId });\n      \n      if (currentValue !== undefined) {\n        localStore.setQuery(api.tasks.get, { taskId }, {\n          ...currentValue,\n          completed: !currentValue.completed,\n        });\n      }\n    }\n  );\n\n  return (\n    \u003Cdiv onClick={() => toggleTask({ taskId: task._id })}>\n      {task.completed ? \"✓\" : \"○\"} {task.title}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### Optimistic Updates for Lists\n\n```typescript\nimport { useMutation } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction useCreateTask(userId: Id\u003C\"users\">) {\n  return useMutation(api.tasks.create).withOptimisticUpdate(\n    (localStore, args) => {\n      const { title, userId } = args;\n      const currentTasks = localStore.getQuery(api.tasks.list, { userId });\n      \n      if (currentTasks !== undefined) {\n        \u002F\u002F Add optimistic task to the list\n        const optimisticTask = {\n          _id: crypto.randomUUID() as Id\u003C\"tasks\">,\n          _creationTime: Date.now(),\n          title,\n          userId,\n          completed: false,\n        };\n        \n        localStore.setQuery(api.tasks.list, { userId }, [\n          optimisticTask,\n          ...currentTasks,\n        ]);\n      }\n    }\n  );\n}\n```\n\n### Cursor-Based Pagination\n\n```typescript\n\u002F\u002F convex\u002Fmessages.ts\nimport { query } from \".\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\nimport { paginationOptsValidator } from \"convex\u002Fserver\";\n\nexport const listPaginated = query({\n  args: {\n    channelId: v.id(\"channels\"),\n    paginationOpts: paginationOptsValidator,\n  },\n  handler: async (ctx, args) => {\n    return await ctx.db\n      .query(\"messages\")\n      .withIndex(\"by_channel\", (q) => q.eq(\"channelId\", args.channelId))\n      .order(\"desc\")\n      .paginate(args.paginationOpts);\n  },\n});\n```\n\n```typescript\n\u002F\u002F React component with pagination\nimport { usePaginatedQuery } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction MessageList({ channelId }: { channelId: Id\u003C\"channels\"> }) {\n  const { results, status, loadMore } = usePaginatedQuery(\n    api.messages.listPaginated,\n    { channelId },\n    { initialNumItems: 20 }\n  );\n\n  return (\n    \u003Cdiv>\n      {results.map((message) => (\n        \u003Cdiv key={message._id}>{message.content}\u003C\u002Fdiv>\n      ))}\n      \n      {status === \"CanLoadMore\" && (\n        \u003Cbutton onClick={() => loadMore(20)}>Load More\u003C\u002Fbutton>\n      )}\n      \n      {status === \"LoadingMore\" && \u003Cdiv>Loading...\u003C\u002Fdiv>}\n      \n      {status === \"Exhausted\" && \u003Cdiv>No more messages\u003C\u002Fdiv>}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### Infinite Scroll Pattern\n\n```typescript\nimport { usePaginatedQuery } from \"convex\u002Freact\";\nimport { useEffect, useRef } from \"react\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction InfiniteMessageList({ channelId }: { channelId: Id\u003C\"channels\"> }) {\n  const { results, status, loadMore } = usePaginatedQuery(\n    api.messages.listPaginated,\n    { channelId },\n    { initialNumItems: 20 }\n  );\n  \n  const observerRef = useRef\u003CIntersectionObserver>();\n  const loadMoreRef = useRef\u003CHTMLDivElement>(null);\n\n  useEffect(() => {\n    if (observerRef.current) {\n      observerRef.current.disconnect();\n    }\n\n    observerRef.current = new IntersectionObserver((entries) => {\n      if (entries[0].isIntersecting && status === \"CanLoadMore\") {\n        loadMore(20);\n      }\n    });\n\n    if (loadMoreRef.current) {\n      observerRef.current.observe(loadMoreRef.current);\n    }\n\n    return () => observerRef.current?.disconnect();\n  }, [status, loadMore]);\n\n  return (\n    \u003Cdiv>\n      {results.map((message) => (\n        \u003Cdiv key={message._id}>{message.content}\u003C\u002Fdiv>\n      ))}\n      \u003Cdiv ref={loadMoreRef} style={{ height: 1 }} \u002F>\n      {status === \"LoadingMore\" && \u003Cdiv>Loading...\u003C\u002Fdiv>}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### Multiple Subscriptions\n\n```typescript\nimport { useQuery } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\n\nfunction Dashboard({ userId }: { userId: Id\u003C\"users\"> }) {\n  \u002F\u002F Multiple subscriptions update independently\n  const user = useQuery(api.users.get, { userId });\n  const tasks = useQuery(api.tasks.list, { userId });\n  const notifications = useQuery(api.notifications.unread, { userId });\n\n  const isLoading = user === undefined || \n                    tasks === undefined || \n                    notifications === undefined;\n\n  if (isLoading) {\n    return \u003Cdiv>Loading...\u003C\u002Fdiv>;\n  }\n\n  return (\n    \u003Cdiv>\n      \u003Ch1>Welcome, {user.name}\u003C\u002Fh1>\n      \u003Cp>You have {tasks.length} tasks\u003C\u002Fp>\n      \u003Cp>{notifications.length} unread notifications\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## Examples\n\n### Real-time Chat Application\n\n```typescript\n\u002F\u002F convex\u002Fmessages.ts\nimport { query, mutation } from \".\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\nexport const list = query({\n  args: { channelId: v.id(\"channels\") },\n  returns: v.array(v.object({\n    _id: v.id(\"messages\"),\n    _creationTime: v.number(),\n    content: v.string(),\n    authorId: v.id(\"users\"),\n    authorName: v.string(),\n  })),\n  handler: async (ctx, args) => {\n    const messages = await ctx.db\n      .query(\"messages\")\n      .withIndex(\"by_channel\", (q) => q.eq(\"channelId\", args.channelId))\n      .order(\"desc\")\n      .take(100);\n\n    \u002F\u002F Enrich with author names\n    return Promise.all(\n      messages.map(async (msg) => {\n        const author = await ctx.db.get(msg.authorId);\n        return {\n          ...msg,\n          authorName: author?.name ?? \"Unknown\",\n        };\n      })\n    );\n  },\n});\n\nexport const send = mutation({\n  args: {\n    channelId: v.id(\"channels\"),\n    authorId: v.id(\"users\"),\n    content: v.string(),\n  },\n  returns: v.id(\"messages\"),\n  handler: async (ctx, args) => {\n    return await ctx.db.insert(\"messages\", {\n      channelId: args.channelId,\n      authorId: args.authorId,\n      content: args.content,\n    });\n  },\n});\n```\n\n```typescript\n\u002F\u002F ChatRoom.tsx\nimport { useQuery, useMutation } from \"convex\u002Freact\";\nimport { api } from \"..\u002Fconvex\u002F_generated\u002Fapi\";\nimport { useState, useRef, useEffect } from \"react\";\n\nfunction ChatRoom({ channelId, userId }: Props) {\n  const messages = useQuery(api.messages.list, { channelId });\n  const sendMessage = useMutation(api.messages.send);\n  const [input, setInput] = useState(\"\");\n  const messagesEndRef = useRef\u003CHTMLDivElement>(null);\n\n  \u002F\u002F Auto-scroll to bottom on new messages\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const handleSend = async (e: React.FormEvent) => {\n    e.preventDefault();\n    if (!input.trim()) return;\n\n    await sendMessage({\n      channelId,\n      authorId: userId,\n      content: input.trim(),\n    });\n    setInput(\"\");\n  };\n\n  return (\n    \u003Cdiv className=\"chat-room\">\n      \u003Cdiv className=\"messages\">\n        {messages?.map((msg) => (\n          \u003Cdiv key={msg._id} className=\"message\">\n            \u003Cstrong>{msg.authorName}:\u003C\u002Fstrong> {msg.content}\n          \u003C\u002Fdiv>\n        ))}\n        \u003Cdiv ref={messagesEndRef} \u002F>\n      \u003C\u002Fdiv>\n      \n      \u003Cform onSubmit={handleSend}>\n        \u003Cinput\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type a message...\"\n        \u002F>\n        \u003Cbutton type=\"submit\">Send\u003C\u002Fbutton>\n      \u003C\u002Fform>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n## Best Practices\n\n- Never run `npx convex deploy` unless explicitly instructed\n- Never run any git commands unless explicitly instructed\n- Use \"skip\" for conditional queries instead of conditionally calling hooks\n- Implement optimistic updates for better perceived performance\n- Use usePaginatedQuery for large datasets\n- Handle undefined state (loading) explicitly\n- Avoid unnecessary re-renders by memoizing derived data\n\n## Common Pitfalls\n\n1. **Conditional hook calls** - Use \"skip\" instead of if statements\n2. **Not handling loading state** - Always check for undefined\n3. **Missing optimistic update rollback** - Optimistic updates auto-rollback on error\n4. **Over-fetching with pagination** - Use appropriate page sizes\n5. **Ignoring subscription cleanup** - React handles this automatically\n\n## References\n\n- Convex Documentation: https:\u002F\u002Fdocs.convex.dev\u002F\n- Convex LLMs.txt: https:\u002F\u002Fdocs.convex.dev\u002Fllms.txt\n- React Client: https:\u002F\u002Fdocs.convex.dev\u002Fclient\u002Freact\n- Optimistic Updates: https:\u002F\u002Fdocs.convex.dev\u002Fclient\u002Freact\u002Foptimistic-updates\n- Pagination: https:\u002F\u002Fdocs.convex.dev\u002Fdatabase\u002Fpagination\n",{"name":5,"next":44,"tags":46,"group":29,"author":47,"version":48,"workflows":49,"description":7,"displayName":51,"prerequisites":52,"user-invocable":54},[45],"convex-security-check",[20,19,32,31,30],"Convex","1.0.0",[50],"full-startup","Convex Realtime",[53],"convex-functions",true,"main","92dddf7f6e7b55d1424795ce11586b05dba9d8c8","skills\u002Fconvex-realtime",3,"https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups","https:\u002F\u002Fgithub.com\u002FCheggin","reaganhsu.com","MTS @datacurve-ai, prev. founding eng @browser-use ","2026-04-24 08:33:47",{"source":8,"skillName":5,"commands":65,"prompt":75,"zipAvailable":54},[66,69,72],{"tool":67,"command":68},"npx","npx skills add Cheggin\u002Frequest-for-startups --skill convex-realtime",{"tool":70,"command":71},"bunx","bunx skills add Cheggin\u002Frequest-for-startups --skill convex-realtime",{"tool":73,"command":74},"pnpm","pnpm dlx skills add Cheggin\u002Frequest-for-startups --skill convex-realtime","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fconvex-realtime\u002FSKILL.md",[],[78,101,121,164,179,202],{"skillNo":79,"name":80,"nameZh":6,"description":81,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":82,"categories":83,"keywords":84,"publishedAt":39},"01M6X7PHK069RZH25A3RXN5049","writer-memory","Agentic memory system for writers - track characters, relationships, scenes, and themes","https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fwriter-memory\u002FSKILL.md",[],[85,86,87,88,89,90,23,91,92,93,94,95,96,97,98,99,100],"memory","writer","writer memory","system","characters","track","themes","memory system","scenes","relationships","query","export","status","validate","init","agentic",{"skillNo":102,"name":103,"nameZh":6,"description":104,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":105,"categories":106,"keywords":107,"publishedAt":39},"01M6X7PC3G9228Z323X5YAKTVW","wiki","LLM Wiki — persistent markdown knowledge base that compounds across sessions (Karpathy model)","https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fwiki\u002FSKILL.md",[],[103,108,109,110,111,112,113,114,115,116,117,118,119,95,120],"wiki lint","knowledge","model","llm wiki","markdown","wiki query","knowledge base","lint","sessions","karpathy","add","across","base",{"skillNo":122,"name":123,"nameZh":6,"description":124,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":125,"categories":126,"keywords":143,"publishedAt":39},"01M6X7P5XFW5C16H3JE4NFQ7MF","website-creation","Build production-quality SaaS websites with opinionated design presets. Use when creating any startup website. The user MUST pick a design style before building. Enforces shadcn\u002Fui, Figma design principles, specific CSS values per style, and anti-AI-writing. Load alongside anti-ai-writing skill. LIGHT MODE ONLY.","https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fwebsite-creation\u002FSKILL.md",[127,132,135,140],{"categoryNo":128,"categoryName":129,"taxonomyType":130,"taxonomyTypeDesc":131},"01JBSEEDCAT000000000000015","视觉设计","SCENARIO","使用场景",{"categoryNo":133,"categoryName":134,"taxonomyType":130,"taxonomyTypeDesc":131},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":136,"categoryName":137,"taxonomyType":138,"taxonomyTypeDesc":139},"01JBSEEDCAT000000000000032","设计师","PROFESSION","适用职业",{"categoryNo":141,"categoryName":142,"taxonomyType":138,"taxonomyTypeDesc":139},"01JBSEEDCAT000000000000036","内容创作者",[144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163],"website creation","website","creation","when","design style","mode","brand-guidelines","full","ui","writing","design principles","only","pick","polish","audit","shadcn","figma","css","Anti-AI","design",{"skillNo":165,"name":166,"nameZh":6,"description":167,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":168,"categories":169,"keywords":170,"publishedAt":39},"01M6X7P3PCEAKA2SBETXHHTNBA","visual-verdict","Structured visual QA verdict for screenshot-to-reference comparisons","https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fvisual-verdict\u002FSKILL.md",[],[171,172,173,23,174,175,176,177,178],"visual verdict","verdict","visual","visual qa","structured","reference","screenshot","qa",{"skillNo":180,"name":181,"nameZh":6,"description":182,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":183,"categories":184,"keywords":193,"publishedAt":39},"01M6X7NXEWWC4NFT0ZK1RRSGES","visual-qa-pipeline","Screenshot every page with Playwright, feed to visual QA agent for design evaluation, report results to Slack. Use after every build phase to verify UI matches the chosen design preset. Integrates with the Playwright skill and the website-creation preset system.","https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fvisual-qa-pipeline\u002FSKILL.md",[185,188,191,192],{"categoryNo":186,"categoryName":187,"taxonomyType":130,"taxonomyTypeDesc":131},"01JBSEEDCAT000000000000005","邮件与沟通",{"categoryNo":189,"categoryName":190,"taxonomyType":130,"taxonomyTypeDesc":131},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":128,"categoryName":129,"taxonomyType":130,"taxonomyTypeDesc":131},{"categoryNo":136,"categoryName":137,"taxonomyType":138,"taxonomyTypeDesc":139},[173,194,174,178,195,196,123,146,151,197,157,198,152,145,177,23,199,163,200,201],"pipeline","visual qa pipeline","slack","page","Evaluation","review","verify ui","phase",{"skillNo":203,"name":204,"nameZh":6,"description":205,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":206,"categories":207,"keywords":208,"publishedAt":39},"01M6X7NSNW00RQ8J531F52YHV9","verify","Verify that a change really works before you claim completion","https:\u002F\u002Fgithub.com\u002FCheggin\u002Frequest-for-startups\u002Fblob\u002Fmain\u002Fskills\u002Fverify\u002FSKILL.md",[],[204,209,25,210,211,212,194,213,214],"deploy-pipeline","you","claim","quality","deploy","change"]