Real-Time Reactivity

The migration is complete. The app reads and writes through Convex. But we have not yet covered real-time sync, which Convex provides without any extra work.

The Demo

Try this:

  1. Open the kanban board in two browser tabs (or two different browsers)
  2. In one tab, add a task
  3. Watch the other tab. The task appears instantly.
  4. Move a task in one tab. It moves in the other tab too.
  5. Delete a task. It is gone from both.

There is no polling here, and no manual refetching, and we did not write any extra code for it. This works because of how Convex is designed.

How It Works

When your component calls useQuery(api.tasks.list), here is what happens behind the scenes:

Browser A                  Convex Cloud                      Browser B
    │                            │                               │
    ├── subscribe(tasks.list) ──▶│◀── subscribe(tasks.list) ─────┤
    │                            │                               │
    │◀── initial results ────────│──── initial results ─────────▶│
    │                            │                               │
    ├── mutation(create) ───────▶│                               │
    │                      (task inserted)                       │
    │                            │                               │
    │   (query re-executed)      │   (query re-executed)         │
    │◀── updated results ────────│──── updated results ─────────▶│
    │                            │                               │
  1. The ConvexReactClient opens a persistent WebSocket connection to the Convex cloud when the app starts.

  2. Each useQuery call creates a subscription: the client requests the results of tasks.list and asks to be notified when they change.

  3. Convex tracks which data each query reads, including that tasks.list reads from the tasks table.

  4. When a mutation modifies the tasks table, Convex automatically identifies all active subscriptions that might be affected.

  5. It re-executes those queries on the server and pushes the new results to every connected client.

  6. The useQuery hook receives the new data and the React component re-renders.

This all happens in milliseconds. The client does not need to know about cache invalidation, stale data, or when to refetch. Convex handles it.

Comparing to Previous Approaches

Think about how we handled data in earlier tutorials:

  • localStorage (KudoBoard): data exists only in one browser. No sharing, no sync.
  • useEffect + fetch (Covid Dashboard): data is fetched once on mount. To see updates, you would need to manually refetch or set up polling.
  • TanStack Query (Shopping Cart): better caching and refetching, but you would still need to invalidate the cache or poll for changes to see updates from other users.

In Convex, real-time sync is the default behavior. You do not opt into it. You would have to opt out of it, and that is almost never what you want.

What This Means for Users

Real-time reactivity enables features that would be complex to build with traditional REST APIs:

  • Collaboration — multiple people working on the same board, seeing each other’s changes instantly
  • Live dashboards — data updates without the user pressing a refresh button
  • Consistent state — no stale data, no “refresh to see latest changes”

For our kanban board, it means that if two teammates are looking at the same board, they always see the same tasks in the same columns. There are no sync issues and no conflicts, and we did not write any extra code to get that.