Development
#react
convex-realtime
Patterns for building reactive apps including subscription management, optimistic updates, cache behavior, and paginated queries with cursor-based loading
DeepseekModel
Curated skill
Quality Excellent · 78
v1.0.0
Get
https://deepseekmodel.com/api/download.php?id=waynesutton-convexskills-skills-convex-realtime-skill-md&format=skill
Download .skill
Standard format with system_prompt and model_config, ready for any agent framework
The actual content of the system_prompt field in the .skill file.
name convex-realtime displayName Convex Realtime description Patterns for building reactive apps including subscription management, optimistic updates, cache behavior, and paginated queries with cursor-based loading version 1.0.0 author Convex tags ["convex","realtime","subscriptions","optimistic-updates","pagination"] Convex Realtime Build reactive applications with Convex's real-time subscriptions, optimistic updates, intelligent caching, and cursor-based pagination. Documentation Sources Before implementing, do not assume; fetch the latest documentation: Primary: https://docs.convex.dev/client/react Optimistic Updates: https://docs.convex.dev/client/react/optimistic-updates Pagination: https://docs.convex.dev/database/pagination For broader context: https://docs.convex.dev/llms.txt Instructions How Convex Realtime Works Automatic Subscriptions - useQuery creates a subscription that updates automatically Smart Caching - Query results are cached and shared across components Consistency - All subscriptions see a consistent view of the database Efficient Updates - Only re-renders when relevant data changes Basic Subscriptions // React component with real-time data import { useQuery } from "convex/react" ; import { api } from "../convex/_generated/api" ; function TaskList ( { userId }: { userId: Id< "users" > } ) { // Automatically subscribes and updates in real-time const tasks = useQuery (api. tasks . list , { userId }); if (tasks === undefined ) { return < div > Loading... </ div > ; } return ( < ul > {tasks.map((task) => ( < li key = {task._id} > {task.title} </ li > ))} </ ul > ); } Conditional Queries import { useQuery } from "convex/react" ; import { api } from "../convex/_generated/api" ; function UserProfile ( { userId }: { userId: Id< "users" > | null } ) { // Skip query when userId is null const user = useQuery ( api. users . get , userId ? { userId } : "skip" ); if (userId === null ) { return < div > Select a user </ div > ; } if (user === undefined ) { return < div > Loading... </ div > ; } return < div > {user.name} </ div > ; } Mutations with Real-time Updates import { useMutation, useQuery } from "convex/react" ; import { api } from "../convex/_generated/api" ; function TaskManager ( { userId }: { userId: Id< "users" > } ) { const tasks = useQuery (api. tasks . list , { userId }); const createTask = useMutation (api. tasks . create ); const toggleTask = useMutation (api. tasks . toggle ); const handleCreate = async ( title : string ) => { // Mutation triggers automatic re-render when data changes await createTask ({ title, userId }); }; const handleToggle = async ( taskId : Id < "tasks" > ) => { await toggleTask ({ taskId }); }; return ( < div > < button onClick = {() => handleCreate("New Task")}>Add Task </ button > < ul > {tasks?.map((task) => ( < li key = {task._id} onClick = {() => handleToggle(task._id)}> {task.completed ? "✓" : "○"} {task.title} </ li > ))} </ ul > </ div > ); } Optimistic Updates Show changes immediately before server confirmation: import { useMutation, useQuery } from "convex/react" ; import { api } from "../convex/_generated/api" ; import { Id } from "../convex/_generated/dataModel" ; function TaskItem ( { task }: { task: Task } ) { const toggleTask = useMutation (api. tasks . toggle ). withOptimisticUpdate ( ( localStore, args ) => { const { taskId } = args; const currentValue = localStore. getQuery (api. tasks . get , { taskId }); if (currentValue !== undefined ) { localStore. setQuery (api. tasks . get , { taskId }, { ...currentValue, completed : !currentValue. completed , }); } } ); return ( < div onClick = {() => toggleTask({ taskId: task._id })}> {task.completed ? "✓" : "○"} {task.title} </ div > ); } Optimistic Updates for Lists import { useMutation } from "convex/react" ; import { api } from "../convex/_generated/api" ; function useCreateTask ( userId : Id < "users" > ) { return useMutation (api. tasks . create ). withOptimisticUpdate ( ( localStore, args ) => { const { title, userId } = args; const currentTasks = localStore. getQuery (api. tasks . list , { userId }); if (currentTasks !== undefined ) { // Add optimistic task to the list const optimisticTask = { _id : crypto. randomUUID () as Id < "tasks" >, _creationTime : Date . now (), title, userId, completed : false , }; localStore. setQuery (api. tasks . list , { userId }, [ optimisticTask, ...currentTasks, ]); } } ); } Cursor-Based Pagination // convex/messages.ts import { query } from "./_generated/server" ; import { v } from "convex/values" ; import { paginationOptsValidator } from "convex/server" ; export const listPaginated = query ({ args : { channelId : v. id ( "channels" ), paginationOpts : paginationOptsValidator, }, handler : async (ctx, args) => { return await ctx. db . query ( "messages" ) . withIndex ( "by_channel" , ( q ) => q. eq ( "channelId" , args. channelId )) . order ( "desc" ) . paginate (args. paginationOpts ); }, }); // React component with pagination import { usePaginatedQuery } from "convex/react" ; import { api } from "../convex/_generated/api" ; function MessageList ( { channelId }: { channelId: Id< "channels" > } ) { const { results, status, loadMore } = usePaginatedQuery ( api. messages . listPaginated , { channelId }, { initialNumItems : 20 } ); return ( < div > {results.map((message) => ( < div key = {message._id} > {message.content} </ div > ))} {status === "CanLoadMore" && ( < button onClick = {() => loadMore(20)}>Load More </ button > )} {status === "LoadingMore" && < div > Loading... </ div > } {status === "Exhausted" && < div > No more messages </ div > } </ div > ); } Infinite Scroll Pattern import { usePaginatedQuery } from "convex/react" ; import { useEffect, useRef } from "react" ; import { api } from "../convex/_generated/api" ; function InfiniteMessageList ( { channelId }: { channelId: Id< "channels" > } ) { const { results, status, loadMore } = usePaginatedQuery ( api. messages . listPaginated , { channelId }, { initialNumItems : 20 } ); const observerRef = useRef< IntersectionObserver >(); const loadMoreRef = useRef< HTMLDivElement >( null ); useEffect ( () => { if (observerRef. current ) { observerRef. current . disconnect (); } observerRef. current = new IntersectionObserver ( ( entries ) => { if (entries[ 0 ]. isIntersecting && status === "CanLoadMore" ) { loadMore ( 20 ); } }); if (loadMoreRef. current ) { observerRef. current . observe (loadMoreRef. current ); } return () => observerRef. current ?. disconnect (); }, [status, loadMore]); return ( < div > {results.map((message) => ( < div key = {message._id} > {message.content} </ div > ))} < div ref = {loadMoreRef} style = {{ height: 1 }} /> {status === "LoadingMore" && < div > Loading... </ div > } </ div > ); } Multiple Subscriptions import { useQuery } from "convex/react" ; import { api } from "../convex/_generated/api" ; function Dashboard ( { userId }: { userId: Id< "users" > } ) { // Multiple subscriptions update independently const user = useQuery (api. users . get , { userId }); const tasks = useQuery (api. tasks . list , { userId }); const notifications = useQuery (api. notifications . unread , { userId }); const isLoading = user === undefined || tasks === undefined || notifications === undefined ; if (isLoading) { return < div > Loading... </ div > ; } return ( < div > < h1 > Welcome, {user.name} </ h1 > < p > You have {tasks.length} tasks </ p > < p > {notifications.length} unread notifications </ p > </ div > ); } Examples Real-time Chat Application // convex/messages.ts import { query, mutation } from "./_generated/server" ; import { v } from "convex/values" ; export const list = query ({ args : { channelId : v. id ( "channels" ) }, returns : v. array (v. object ({ _id : v. id ( "messages" ), _creationTime : v. number (), content : v. string (), authorId : v. id ( "users" ), authorName : v. string (), })), handler : async (ctx, args) => { const messages = await ctx. db . query ( "messages" ) . withIndex ( "by_channel" , ( q ) => q. eq ( "channelId" , args. channelId )) . order ( "desc" ) . take ( 100 ); // Enrich with author names return Promise . all ( messages. map ( async (msg) => { const author = await ctx. db . get (msg. authorId ); return { ...msg, authorName : author?. name ?? "Unknown" , }; }) ); }, }); export const send = mutation ({ args : { channelId : v. id ( "channels" ), authorId : v. id ( "users" ), content : v. string (), }, returns : v. id ( "messages" ), handler : async (ctx, args) => { return await ctx. db . insert ( "messages" , { channelId : args. channelId , authorId : args. authorId , content : args. content , }); }, }); // ChatRoom.tsx import { useQuery, useMutation } from "convex/react" ; import { api } from "../convex/_generated/api" ; import { useState, useRef, useEffect } from "react" ; function ChatRoom ( { channelId, userId }: Props ) { const messages = useQuery (api. messages . list , { channelId }); const sendMessage = useMutation (api. messages . send ); const [input, setInput] = useState ( "" ); const messagesEndRef = useRef< HTMLDivElement >( null ); // Auto-scroll to bottom on new messages useEffect ( () => { messagesEndRef. current ?. scrollIntoView ({ behavior : "smooth" }); }, [messages]); const handleSend = async ( e : React . FormEvent ) => { e. preventDefault ();
Keywords that activate this skill. Click one to copy it.
This skill does not provide trigger words.
The downloaded .skill package contains the following fields.
| Field | Description |
|---|---|
| format | Format tag (skill/v1) |
| skill_id | Unique skill ID |
| name | Skill name |
| version | Version |
| description | Description |
| category | Categories (array) |
| trigger_words | Trigger words |
| tags | Tags |
| source | Source |
| source_url | Source URL (this page) |
| exported_at | Exported at (set per download) |
| system_prompt | System prompt body |
| model_config | Model config: provider / model / temperature / max_tokens / top_p |
| examples | Examples |
| install_guide | Import guide for Coze / Dify / Claude / custom frameworks |
The same skill can be exported in different platform formats.