Patterns for building reactive apps including subscription management, optimistic updates, cache behavior, and paginated queries with cursor-based loading
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 dataimport { 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 });
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(
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 =
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
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,
Cursor-Based Pagination
// convex/messages.tsimport { 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,
// React component with paginationimport { usePaginatedQuery } from "convex/react";import { api } from "../convex/_generated/api";function MessageList({ channelId }: { channelId: Id<"channels"> }) { const { results
Infinite Scroll Pattern
import { usePaginatedQuery } from "convex/react";import { useEffect, useRef } from "react";import { api } from "../convex/_generated/api";function InfiniteMessageList({ channelId }:
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 =
Examples
Real-time Chat Application
// convex/messages.tsimport { query, mutation } from "./_generated/server";import { v } from "convex/values";export const list = query({ args: { channelId: v.id("channels"
// ChatRoom.tsximport { useQuery, useMutation } from "convex/react";import { api } from "../convex/_generated/api";import { useState, useRef, useEffect } from "react";function ChatRoom
Best Practices
Never run npx convex deploy unless explicitly instructed
Never run any git commands unless explicitly instructed
Use "skip" for conditional queries instead of conditionally calling hooks
Implement optimistic updates for better perceived performance
Use usePaginatedQuery for large datasets
Handle undefined state (loading) explicitly
Avoid unnecessary re-renders by memoizing derived data
Common Pitfalls
Conditional hook calls - Use "skip" instead of if statements
Not handling loading state - Always check for undefined
Missing optimistic update rollback - Optimistic updates auto-rollback on error
Over-fetching with pagination - Use appropriate page sizes
Ignoring subscription cleanup - React handles this automatically