import { ConvexProvider, ConvexReactClient } from 'convex/react';
const convex = new ConvexReactClient(process.env.REACT_APP_CONVEX_URL!);
function App() {
return (
<ConvexProvider client={convex}>
<YourApp />
</ConvexProvider>
);
}import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';
function Messages() {
const messages = useQuery(api.messages.list);
if (messages === undefined) return <div>Loading...</div>;
return (
<div>
{messages.map(m => <p key={m._id}>{m.body}</p>)}
</div>
);
}
// With arguments
function UserMessages({ userId }: { userId: string }) {
const messages = useQuery(api.messages.getByUser, { userId });
// ...
}
// Skip conditionally
function ConditionalData({ shouldLoad }: { shouldLoad: boolean }) {
const data = useQuery(api.data.get, shouldLoad ? {} : 'skip');
// Returns undefined when skipped
}import { useMutation } from 'convex/react';
function SendMessage() {
const send = useMutation(api.messages.send);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await send({ author: 'Alice', body: 'Hello!' });
};
return (
<form onSubmit={handleSubmit}>
<button type="submit">Send</button>
</form>
);
}
// With error handling
function CreatePost() {
const create = useMutation(api.posts.create);
const [error, setError] = useState<string | null>(null);
const handleCreate = async (title: string, body: string) => {
try {
await create({ title, body });
setError(null);
} catch (err) {
setError(err.message);
}
};
return error ? <div>Error: {error}</div> : <button onClick={...}>Create</button>;
}import { useMutation } from 'convex/react';
function LikeButton({ postId }: { postId: string }) {
const like = useMutation(api.posts.like)
.withOptimisticUpdate((store, args) => {
const posts = store.getQuery(api.posts.list);
if (posts) {
store.setQuery(api.posts.list, {}, posts.map(p =>
p._id === args.postId ? { ...p, likes: p.likes + 1 } : p
));
}
});
return <button onClick={() => like({ postId })}>Like</button>;
}
// With paginated queries
import { usePaginatedQuery, optimisticallyUpdateValueInPaginatedQuery } from 'convex/react';
function MessageList() {
const { results, loadMore, status } = usePaginatedQuery(
api.messages.list,
{},
{ initialNumItems: 20 }
);
const like = useMutation(api.messages.like)
.withOptimisticUpdate((store, args) => {
optimisticallyUpdateValueInPaginatedQuery(
store,
api.messages.list,
{},
msg => msg._id === args.id ? { ...msg, likes: msg.likes + 1 } : msg
);
});
return (
<div>
{results.map(m => (
<div key={m._id}>
{m.body} ({m.likes} likes)
<button onClick={() => like({ id: m._id })}>Like</button>
</div>
))}
</div>
);
}import { useAction } from 'convex/react';
function SendEmail() {
const sendEmail = useAction(api.emails.send);
const [loading, setLoading] = useState(false);
const handleSend = async () => {
setLoading(true);
try {
await sendEmail({ to: 'user@example.com', subject: 'Hi', body: 'Hello!' });
} finally {
setLoading(false);
}
};
return <button onClick={handleSend} disabled={loading}>Send</button>;
}import { usePaginatedQuery } from 'convex/react';
function InfiniteList() {
const { results, status, loadMore } = usePaginatedQuery(
api.items.list,
{},
{ initialNumItems: 20 }
);
return (
<div>
{results.map(item => <div key={item._id}>{item.name}</div>)}
{status === 'CanLoadMore' && (
<button onClick={() => loadMore(20)}>Load More</button>
)}
{status === 'LoadingMore' && <div>Loading...</div>}
{status === 'Exhausted' && <div>No more items</div>}
</div>
);
}
// With intersection observer
function AutoLoadList() {
const { results, status, loadMore } = usePaginatedQuery(
api.items.list,
{},
{ initialNumItems: 20 }
);
const loadMoreRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
entries => {
if (entries[0].isIntersecting && status === 'CanLoadMore') {
loadMore(20);
}
},
{ threshold: 1.0 }
);
if (loadMoreRef.current) observer.observe(loadMoreRef.current);
return () => observer.disconnect();
}, [status, loadMore]);
return (
<div>
{results.map(item => <div key={item._id}>{item.name}</div>)}
{status !== 'Exhausted' && <div ref={loadMoreRef}>Loading...</div>}
</div>
);
}import { useConvexAuth, Authenticated, Unauthenticated } from 'convex/react';
function App() {
const { isLoading, isAuthenticated } = useConvexAuth();
if (isLoading) return <div>Loading...</div>;
return (
<div>
<Authenticated>
<Dashboard />
</Authenticated>
<Unauthenticated>
<Login />
</Unauthenticated>
</div>
);
}import { useConvex } from 'convex/react';
function Component() {
const convex = useConvex();
useEffect(() => {
// Set auth
convex.setAuth(async () => await getToken());
// Clear auth
// convex.clearAuth();
}, [convex]);
}import { useConvexConnectionState } from 'convex/react';
function ConnectionIndicator() {
const { isWebSocketConnected, hasInflightRequests } = useConvexConnectionState();
return (
<div>
{!isWebSocketConnected && <div>Reconnecting...</div>}
{hasInflightRequests && <div>Loading...</div>}
</div>
);
}import { ConvexClient } from 'convex/browser';
import { api } from './convex/_generated/api';
const client = new ConvexClient('https://your-deployment.convex.cloud');
// One-time query
const messages = await client.query(api.messages.list);
// Reactive subscription
const unsubscribe = client.onUpdate(
api.messages.list,
{},
(messages) => {
console.log('Updated:', messages);
updateUI(messages);
}
);
// Later: clean up
unsubscribe();
// Execute mutation
const id = await client.mutation(api.messages.send, {
author: 'Alice',
body: 'Hello!',
});
// HTTP-only client (no WebSocket)
import { ConvexHttpClient } from 'convex/browser';
const httpClient = new ConvexHttpClient('https://your-deployment.convex.cloud');
const messages = await httpClient.query(api.messages.list, {});function Dashboard() {
const messages = useQuery(api.messages.list);
const users = useQuery(api.users.list);
const stats = useQuery(api.stats.get);
if (!messages || !users || !stats) return <div>Loading...</div>;
return <div>{/* Use all data */}</div>;
}
// Alternative: useQueries
import { useQueries } from 'convex/react';
const { messages, users, stats } = useQueries({
messages: { query: api.messages.list, args: {} },
users: { query: api.users.list, args: {} },
stats: { query: api.stats.get, args: {} },
});function UserPosts({ userId }: { userId: string }) {
const user = useQuery(api.users.get, { id: userId });
const posts = useQuery(
api.posts.getByUser,
user ? { userId: user._id } : 'skip'
);
if (!user) return <div>Loading user...</div>;
if (!posts) return <div>Loading posts...</div>;
return <div>{/* Render */}</div>;
}import { useMutation } from 'convex/react';
import { useNavigate } from 'react-router-dom';
function CreatePost() {
const navigate = useNavigate();
const create = useMutation(api.posts.create);
const handleCreate = async (data: any) => {
const postId = await create(data);
navigate(`/posts/${postId}`);
};
return <button onClick={() => handleCreate({...})}>Create</button>;
}function CommentForm({ postId }: { postId: string }) {
const addComment = useMutation(api.comments.add);
const [text, setText] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!text.trim()) return;
await addComment({ postId, text });
setText(''); // Clear form
};
return (
<form onSubmit={handleSubmit}>
<textarea value={text} onChange={e => setText(e.target.value)} />
<button type="submit">Add Comment</button>
</form>
);
}