[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6FJZ74AT2RF576P189PMJ64":3,"skill-same:01M6FJZ74AT2RF576P189PMJ64":48},{"skillNo":4,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":21,"linkOnly":22,"markdownContent":23,"markdownBody":23,"frontMatter":24,"refName":25,"commitSha":26,"skillDirectory":27,"forksCount":28,"repositoryHtmlUrl":29,"repositoryHomepageUrl":30,"ownerHtmlUrl":31,"ownerBlogUrl":6,"ownerDescription":32,"updatedAt":33,"install":34,"resources":47},"01M6FJZ74AT2RF576P189PMJ64","convex-realtime",null,"ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fwaynesutton\u002Fconvexskills\u002Fconvex-realtime\u002FSKILL.md",[],[18,19,20],"convex","realtime","convex realtime","2026-11-10 10:12:30",false,"# 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",{},"main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Fwaynesutton\u002Fconvexskills\u002Fconvex-realtime",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":7,"skillName":5,"commands":35,"prompt":45,"zipAvailable":46},[36,39,42],{"tool":37,"command":38},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill convex-realtime",{"tool":40,"command":41},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill convex-realtime",{"tool":43,"command":44},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill convex-realtime","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fwaynesutton\u002Fconvexskills\u002Fconvex-realtime\u002FSKILL.md",true,[],[49,63,73,82,88,95],{"skillNo":50,"name":51,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":52,"categories":53,"keywords":59,"publishedAt":21},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[54],{"categoryNo":55,"categoryName":56,"taxonomyType":57,"taxonomyTypeDesc":58},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[60,61,62],"model","model hierarchy","model hierarchy skill",{"skillNo":64,"name":65,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":66,"categories":67,"keywords":69,"publishedAt":21},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[68],{"categoryNo":55,"categoryName":56,"taxonomyType":57,"taxonomyTypeDesc":58},[70,71,72],"MCP","zoom","zoom mcp",{"skillNo":74,"name":75,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":76,"categories":77,"keywords":78,"publishedAt":21},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[79,80,81,71],"sdk","zoom apps sdk","apps",{"skillNo":83,"name":84,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":85,"categories":86,"keywords":87,"publishedAt":21},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[84],{"skillNo":89,"name":90,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":91,"categories":92,"keywords":94,"publishedAt":21},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[93],{"categoryNo":55,"categoryName":56,"taxonomyType":57,"taxonomyTypeDesc":58},[90],{"skillNo":96,"name":97,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":98,"categories":99,"keywords":100,"publishedAt":21},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[97]]