Skip to main content

Client Libraries

Your application interacts with Convex via a client library.

The client library lives within your front-end code and is your app's interface for:

  1. Loading data reactively from query functions.
  2. Editing data by calling mutation functions.

This documentation focuses on the React client, but we plan to add support for more frameworks and platforms in the future. If there is a client framework or platform you wish we'd supported, let us know on Slack!

Connecting to Convex

The ConvexReactClient connects to your Convex deployment by creating a WebSocket. This is a 2-way communication channel over TCP. This is important because it allows Convex to push new query results reactively to the client without the client needing to poll for updates.

If the internet connection drops, the client will handle reconnecting and re-establishing the Convex session automatically.

We recommend storing this client in a React context using the ConvexProvider component.

Loading Data

Your application loads data from Convex using the useQuery React hook. This hook allows your app to invoke query functions within your Convex deployment.


The useQuery hook brings automatic reactivity to your UI components.

Modern reactive web development frameworks like React make it easy to rerender UI components when local state changes, but building a fully reactive app also requires propagating changes from database state all the way to the UI.

The useQuery hook sends a WebSocket message to the Convex deployment to create an active subscription. Convex transparently monitors all data that a query depends on and automatically updates components if the query result changes.

If you want to display a dynamic view of all chat messages returned by a Convex query function called listMessages, all you need to write is:

function ChatView() {
const messages = useQuery("listMessages");

return => {
return <div>{message}</div>;

Under the hood, this useQuery React hook:

  1. Tells the deployment to run listMessages query function. As part of execution, Convex will track which documents and table ranges were used to compute the result. We call this the read set.
  2. Creates a subscription in the deployment to track this client's continued interest in the query.

Later on, if any mutation inserts, updates, or deletes a record that overlaps with the read set, Convex knows it needs to recompute the listMessages query. If the result of listMessages changes, the new value is synced to the client and the component rerenders.

When the useQuery hook unmounts, the Convex client will inform the deployment that it no longer needs this subscription.

This dataflow ensures your components are automatically re-rendered anytime the underlying data changes.

Consistent Views

Convex subscriptions provide a powerful mechanism to keep dynamic UI components up to date, but a naive implementation of subscriptions could expose consistency anomalies to the user.

A consistency anomaly occurs when the data representation on the client doesn't correspond to a valid snapshot of data on the backend.

A naive app using a basic HTTP API normally has 2 types of consistency anomalies:

  1. Two different HTTP requests might have inconsistent data because they were executed as slightly different times and the underlying data changed in the meantime.
  2. A single request HTTP may contain inconsistent data on it's own! This happens when the underlying data changed while this single request was being executed on the backend.

These anomalies can create a lot of subtle bugs on the frontend. Many frameworks require developers to implement hacks and workarounds on the frontend to handle data scenarios that can never exists on the backend. For example:

  • A chat message might reference a user ID that doesn't exist yet on the frontend.
  • A list of chat messages might have different names for the same user.

Handling these cases manually both complicates frontend logic and can creates confusing UI for users.

Fortunately none of these issues are a problem with Convex, since we automatically ensure that all your queries are consistent. Convex tracks all subscriptions simultaneously and synchronizes all changes to the client in a consistent order: if the query results backing one component changes before the query for another, they will be updated locally in this respective order. Moreover, if a single mutation updates multiple queries simultaneously, these changes will propagate to the client atomically. Convex guarantees that clients always see a consistent view of backend data, representing the database state at a point in time.

Convex handles the heavy lifting for you so you don't have to think about this complexity. Your subscriptions just work.

Editing Data

Your application edits its data in Convex using the useMutation React hook. This hook allows your app to invoke mutation functions within your Convex deployment.


The client automatically handles retrying mutations until they are saved.

Internally, your Convex deployment keeps track of which mutations have already been committed and ensures that each mutation is only executed once, even if the client retries it. We call this mutation idempotence.

Mutation idempotence is important because it solves a common class of bugs where a flaky internet connection and naive retry logic cause a mutation to accidentally get executed twice. If you've had a message sent twice in a chat app you've experienced this bug. Mutation idempotence guarantees that this class of bugs cannot happen with Convex.

Additionally, while the client has outstanding mutations in flight, the client will warn users before they close their browser tab. This means that when you call a Convex mutation, you can be sure that the user’s edits won’t be lost.

Optimistic Updates

As discussed above, Convex queries are fully reactive, so all query results will be automatically updated after a mutation.

Sometimes you may want to update the UI before a mutation has even been synced to the Convex deployment. To accomplish this, you can configure an optimistic update to execute as part of your mutation.

Optimistic updates are temporary, local changes to your query results which are used to make your app more responsive.

Internally, optimistic updates act as an overlay layer on top of the original query results from the server. The client always keeps track of the original server query results and will roll back the optimistic update after the relevant mutation completes and the new query results are synced.

If you want to learn how to write optimistic updates, see Optimistic Updates