Skip to content

Instantly share code, notes, and snippets.

@vasco3
Created October 6, 2025 23:47
Show Gist options
  • Save vasco3/f499f7c5a453b04e1be75bb6b6964f76 to your computer and use it in GitHub Desktop.
Save vasco3/f499f7c5a453b04e1be75bb6b6964f76 to your computer and use it in GitHub Desktop.
convex_tanstack_guidelines.md
# Convex + TanStack Start Integration Guidelines
Esta sección describe cómo integrar Convex eficientemente con TanStack Start, cubriendo los patrones más comunes para queries, mutations y actions.
## Setup inicial
Antes de usar estos patrones, asegúrate de tener configurado el ConvexProvider en tu aplicación TanStack Start:
```ts
// src/root.tsx o src/router.tsx
import { ConvexProvider, ConvexReactClient } from "convex/react";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL!);
export function App() {
return <ConvexProvider client={convex}>{/* Tu aplicación */}</ConvexProvider>;
}
```
## Patrón 1: convexQuery + useQuery
Este patrón combina los hooks de Convex con TanStack Query directamente en los componentes para obtener control avanzado sobre el caché y el estado de las queries.
- Note: useQuery can be "disabled" by passing in "skip" instead of its arguments.
### Ejemplo básico
```ts
// components/MessageList.tsx
import { convexQuery } from "@convex-dev/react-query";
import { useQuery } from "@tanstack/react-query";
import { api } from "convex/_generated/api";
import { Id } from "convex/_generated/dataModel";
interface MessageListProps {
channelId: Id<"channels">;
}
export function MessageList({ channelId }: MessageListProps) {
const {
data: messages,
isLoading,
error,
} = useQuery(convexQuery(api.messages.listMessages, { channelId }));
if (isLoading) return <div>Cargando mensajes...</div>;
if (error) return <div>Error al cargar mensajes: {error.message}</div>;
return (
<div>
{messages?.map((message) => (
<div key={message._id} className="p-2 border rounded">
{message.content}
</div>
))}
</div>
);
}
```
### Ejemplo con skip condicional
> Loading a query conditionally
- useQuery can be "disabled" by passing in "skip" instead of its arguments:
```ts
// Ejemplo simple de skip basado en parámetros de URL
import { useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
export function App() {
const param = new URLSearchParams(window.location.search).get("param");
const data = useQuery(
api.functions.read,
param !== null ? { param } : "skip"
);
//...
}
```
### Ejemplo avanzado con opciones de TanStack Query
```ts
// components/MessageListAdvanced.tsx
import { convexQuery } from "@convex-dev/react-query";
import { useQuery } from "@tanstack/react-query";
import { api } from "convex/_generated/api";
import { Id } from "convex/_generated/dataModel";
interface MessageListAdvancedProps {
channelId: Id<"channels">;
}
export function MessageListAdvanced({ channelId }: MessageListAdvancedProps) {
const { data: messages } = useQuery(
convexQuery(api.messages.listMessages, { channelId })
);
return (
<div>
<div>
<h3>Mensajes ({messages?.length || 0})</h3>
<button onClick={() => refetch()}>Actualizar</button>
</div>
{messages?.map((message) => (
<div key={message._id}>{message.content}</div>
))}
</div>
);
}
```
## Patrón 2: useConvexMutation + useMutation
Este patrón permite usar mutations de Convex directamente en componentes con las capacidades avanzadas de TanStack Query como optimistic updates y invalidación inteligente de caché.
### Ejemplo básico
```ts
// components/MessageForm.tsx
import { useState } from "react";
import { useConvexMutation } from "@convex-dev/react-query";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { api } from "convex/_generated/api";
import { Id } from "convex/_generated/dataModel";
interface MessageFormProps {
channelId: Id<"channels">;
authorId: Id<"users">;
}
export function MessageForm({ channelId, authorId }: MessageFormProps) {
const [content, setContent] = useState("");
const queryClient = useQueryClient();
const createMessage = useMutation({
mutationFn: useConvexMutation(api.messages.sendMessage),
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!content.trim()) return;
try {
await createMessage.mutateAsync({
channelId,
authorId,
content: content.trim(),
});
} catch (error) {
// Error ya manejado por la configuración del useMutation
console.error("Error enviando mensaje:", error);
}
};
return (
<form onSubmit={handleSubmit}>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="Escribe tu mensaje..."
disabled={createMessage.isPending}
/>
<button
type="submit"
disabled={createMessage.isPending || !content.trim()}
>
{createMessage.isPending ? "Enviando..." : "Enviar"}
</button>
{createMessage.error && <div>Error: {createMessage.error.message}</div>}
</form>
);
}
```
## Patrón 3: useConvexAction + useMutation
Este patrón es ideal para actions de Convex que realizan operaciones complejas o requieren integración con servicios externos, implementado directamente en los componentes.
### Ejemplo básico
```ts
// components/AIResponseGenerator.tsx
import { useConvexAction } from "@convex-dev/react-query";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { api } from "convex/_generated/api";
import { Id } from "convex/_generated/dataModel";
interface AIResponseGeneratorProps {
channelId: Id<"channels">;
}
export function AIResponseGenerator({ channelId }: AIResponseGeneratorProps) {
const queryClient = useQueryClient();
const generateResponse = useMutation({
mutationFn: useConvexAction(api.messages.generateResponse),
});
const handleGenerate = async () => {
try {
await generateResponse.mutateAsync({ channelId });
} catch (error) {
// Error ya manejado por la configuración del useMutation
console.error("Error generando respuesta:", error);
}
};
return (
<div>
<button onClick={handleGenerate} disabled={generateResponse.isPending}>
{generateResponse.isPending
? "Generando respuesta..."
: "Generar respuesta IA"}
</button>
</div>
);
}
```
### Ejemplo avanzado con polling y progreso
```ts
// components/DocumentProcessor.tsx
import { useConvexAction, convexQuery } from "@convex-dev/react-query";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { api } from "convex/_generated/api";
import { Id } from "convex/_generated/dataModel";
interface DocumentProcessorProps {
documentId: Id<"documents">;
}
export function DocumentProcessor({ documentId }: DocumentProcessorProps) {
const queryClient = useQueryClient();
// Action para procesar documento
const processDocument = useMutation({
mutationFn: useConvexAction(api.documents.processDocument),
});
// Query para monitorear progreso con polling
const { data: progress } = useQuery(
convexQuery(
api.documents.getProgress,
!!documentId ? { documentId } : "skip"
)
);
const handleProcess = async () => {
try {
await processDocument.mutateAsync({ documentId });
// Puedes ejecutar código adicional después del éxito aquí
} catch (error) {
// Los errores se manejan en la configuración del mutation
console.error("Error procesando documento:", error);
}
};
return (
<div>
<div>
<button
onClick={handleProcess}
disabled={
processDocument.isPending || progress?.status === "processing"
}
>
{progress?.status === "processing"
? "Procesando..."
: "Procesar Documento"}
</button>
{progress?.status && <span>Estado: {progress.status}</span>}
</div>
{progress?.status === "processing" && (
<div>
<div>
<span>Progreso</span>
<span>{progress.progress || 0}%</span>
</div>
<div>
<div style={{ width: `${progress.progress || 0}%` }} />
</div>
</div>
)}
{progress?.status === "error" && (
<div>
<div>Error procesando documento</div>
<div>{progress.error}</div>
<button onClick={handleProcess}>Reintentar</button>
</div>
)}
{progress?.status === "completed" && (
<div>
<div>¡Documento procesado exitosamente!</div>
{progress.result && (
<div>Resultado: {JSON.stringify(progress.result)}</div>
)}
</div>
)}
{processDocument.error && (
<div>Error del sistema: {processDocument.error.message}</div>
)}
</div>
);
}
```
## Mejores prácticas
### 1. Manejo centralizado de errores
Usa sonner toast para mostrar notificaciones
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment