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:
- Loading data reactively from query functions.
- Editing data by calling mutation functions.
note
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 Discord!
Connecting to Convex
The ConvexReactClient
connects to
Convex 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.
Reactivity
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 Convex 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 messages.map(message => {
return <div>{message}</div>;
});
}
Under the hood, this useQuery
React hook:
- Tells Convex 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. - Creates a subscription in Convex 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 Convex 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:
- Two different HTTP requests might have inconsistent data because they were executed as slightly different times and the underlying data changed in the meantime.
- 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 Convex.
Retries
The client automatically handles retrying mutations until they are saved.
Internally, Convex 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 Convex. 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