Documentation Index Fetch the complete documentation index at: https://mintlify.com/rjdellecese/confect/llms.txt
Use this file to discover all available pages before exploring further.
Confect provides React hooks that seamlessly integrate with Effect schemas, giving you end-to-end type safety from your database to your UI components. These hooks handle encoding/decoding automatically and provide real-time updates.
Installation
npm install @confect/react
Setup
Wrap your application with ConvexProvider from convex/react:
import { ConvexProvider , ConvexReactClient } from "convex/react" ;
const App = () => {
const convexClient = new ConvexReactClient ( import . meta . env . VITE_CONVEX_URL );
return (
< ConvexProvider client = { convexClient } >
< YourApp />
</ ConvexProvider >
);
};
Core Hooks
Confect provides three primary hooks for interacting with your backend:
useQuery Subscribe to real-time data
useMutation Modify data transactionally
useAction Execute long-running operations
useQuery
Subscribe to query results with automatic updates. The hook returns undefined while loading, then the decoded result.
Type Signature
const useQuery = < Query extends Ref . AnyPublicQuery >(
ref : Query ,
args : Ref . Args < Query >[ "Type" ],
): Ref . Returns < Query > [ "Type" ] | undefined
Example: Display a List
import { useQuery } from "@confect/react" ;
import refs from "../confect/_generated/refs" ;
const NoteList = () => {
const notes = useQuery ( refs . public . notesAndRandom . notes . list , {});
if ( notes === undefined ) {
return < p > Loading… </ p > ;
}
return (
< ul >
{ notes . map (( note ) => (
< li key = { note . _id } >
< p > { note . text } </ p >
< p > Created: {new Date ( note . _creationTime ). toLocaleString () } </ p >
</ li >
)) }
</ ul >
);
};
How It Works
Automatic encoding : Arguments are encoded using your Effect schema
Real-time subscription : Convex keeps the data synchronized
Automatic decoding : Results are decoded back to TypeScript types
Type safety : Full inference from function reference to result type
The query automatically re-runs when dependencies change, keeping your UI in sync with the database.
useMutation
Execute transactional database operations. Returns a function you can call to trigger the mutation.
Type Signature
const useMutation = < Mutation extends Ref . AnyPublicMutation >(
ref : Mutation ,
) => (
args : Ref . Args < Mutation >[ "Type" ],
) => Promise < Ref . Returns < Mutation > [ "Type" ] >
Example: Create and Delete
import { useMutation } from "@confect/react" ;
import { useState } from "react" ;
import refs from "../confect/_generated/refs" ;
const NoteForm = () => {
const [ note , setNote ] = useState ( "" );
const insertNote = useMutation ( refs . public . notesAndRandom . notes . insert );
const deleteNote = useMutation ( refs . public . notesAndRandom . notes . delete_ );
const handleSubmit = async () => {
// insertNote is fully typed - TypeScript knows the args and return type
const noteId = await insertNote ({ text: note });
setNote ( "" );
console . log ( `Created note with ID: ${ noteId } ` );
};
const handleDelete = async ( noteId : string ) => {
await deleteNote ({ noteId });
};
return (
< div >
< textarea
value = { note }
onChange = { ( e ) => setNote ( e . target . value ) }
placeholder = "Write a note..."
/>
< button onClick = { handleSubmit } > Save Note </ button >
</ div >
);
};
Mutation Properties
Transactional : All database operations succeed or fail together
Optimistic updates : Queries automatically reflect changes
Error handling : Promise rejections for failed mutations
Mutations run in a transaction and cannot make external HTTP requests. Use actions for non-transactional work.
useAction
Execute actions that can run longer operations, make HTTP requests, or call third-party APIs.
Type Signature
const useAction = < Action extends Ref . AnyPublicAction >(
ref : Action ,
) => (
args : Ref . Args < Action >[ "Type" ],
) => Promise < Ref . Returns < Action > [ "Type" ] >
Example: Call External API
import { useAction } from "@confect/react" ;
import { useEffect , useState } from "react" ;
import refs from "../confect/_generated/refs" ;
const RandomNumber = () => {
const [ randomNumber , setRandomNumber ] = useState < number | null >( null );
const [ loading , setLoading ] = useState ( false );
const getRandom = useAction ( refs . public . notesAndRandom . random . getNumber );
const fetchNumber = async () => {
setLoading ( true );
try {
const number = await getRandom ({});
setRandomNumber ( number );
} catch ( error ) {
console . error ( "Failed to fetch random number:" , error );
} finally {
setLoading ( false );
}
};
useEffect (() => {
fetchNumber ();
}, []);
return (
< div >
< p > Random number: { randomNumber ?? "Loading…" } </ p >
< button onClick = { fetchNumber } disabled = { loading } >
{ loading ? "Fetching…" : "Get New Number" }
</ button >
</ div >
);
};
Example: Send Email
import { useAction } from "@confect/react" ;
import { useState } from "react" ;
import refs from "../confect/_generated/refs" ;
const EmailSender = () => {
const [ status , setStatus ] = useState < string | null >( null );
const sendEmail = useAction ( refs . public . node . email . send );
const handleSend = async () => {
setStatus ( "Sending…" );
try {
await sendEmail ({
to: "user@example.com" ,
subject: "Welcome to Confect" ,
body: "Thanks for trying Confect!" ,
});
setStatus ( "Email sent successfully!" );
} catch ( error ) {
setStatus ( `Error: ${ String ( error ) } ` );
}
};
return (
< div >
< button onClick = { handleSend } > Send Email </ button >
{ status && < p > { status } </ p > }
</ div >
);
};
Action Capabilities
HTTP requests : Call external APIs and webhooks
Long-running : No strict time limits like mutations
Non-transactional : Can have side effects
Node runtime : Access to Node.js APIs in Node actions
Effect Schema Integration
All hooks work seamlessly with Effect schemas, providing automatic validation and transformation.
Define Your Schema
import { FunctionSpec , GenericId , GroupSpec } from "@confect/core" ;
import { Schema } from "effect" ;
import { Notes } from "../tables/Notes" ;
export const notes = GroupSpec . make ( "notes" )
. addFunction (
FunctionSpec . publicMutation ({
name: "insert" ,
args: Schema . Struct ({ text: Schema . String }),
returns: GenericId . GenericId ( "notes" ),
}),
)
. addFunction (
FunctionSpec . publicQuery ({
name: "list" ,
args: Schema . Struct ({}),
returns: Schema . Array ( Notes . Doc ),
}),
);
Use in Components
import { useQuery , useMutation } from "@confect/react" ;
import refs from "../confect/_generated/refs" ;
// TypeScript knows the exact shape of the data
const notes = useQuery ( refs . public . notesAndRandom . notes . list , {});
// Type: Array<{ _id: string; _creationTime: number; text: string }> | undefined
const insertNote = useMutation ( refs . public . notesAndRandom . notes . insert );
// Type: (args: { text: string }) => Promise<string>
Confect automatically handles encoding and decoding between TypeScript types and database representations using your Effect schemas.
Complete Example
Here’s a full-featured component using all three hooks:
import { useAction , useMutation , useQuery } from "@confect/react" ;
import { useState } from "react" ;
import refs from "../confect/_generated/refs" ;
const NotesApp = () => {
const [ note , setNote ] = useState ( "" );
// Query: Real-time list of notes
const notes = useQuery ( refs . public . notesAndRandom . notes . list , {});
// Mutations: Transactional database operations
const insertNote = useMutation ( refs . public . notesAndRandom . notes . insert );
const deleteNote = useMutation ( refs . public . notesAndRandom . notes . delete_ );
// Action: External API call
const getRandom = useAction ( refs . public . notesAndRandom . random . getNumber );
const handleInsert = async () => {
await insertNote ({ text: note });
setNote ( "" );
};
const handleDelete = async ( noteId : string ) => {
await deleteNote ({ noteId });
};
const addRandomNote = async () => {
const randomNumber = await getRandom ({});
await insertNote ({ text: `Random number: ${ randomNumber } ` });
};
if ( notes === undefined ) {
return < div > Loading notes… </ div > ;
}
return (
< div >
< h1 > My Notes </ h1 >
{ /* Create form */ }
< div >
< textarea
value = { note }
onChange = { ( e ) => setNote ( e . target . value ) }
placeholder = "Write a note..."
/>
< button onClick = { handleInsert } > Add Note </ button >
< button onClick = { addRandomNote } > Add Random Note </ button >
</ div >
{ /* Notes list */ }
< ul >
{ notes . map (( note ) => (
< li key = { note . _id } >
< p > { note . text } </ p >
< button onClick = { () => handleDelete ( note . _id ) } >
Delete
</ button >
</ li >
)) }
</ ul >
</ div >
);
};
export default NotesApp ;
Real-Time Updates
Queries automatically update when underlying data changes:
import { useQuery } from "@confect/react" ;
import refs from "../confect/_generated/refs" ;
const LiveCounter = () => {
// This automatically updates when notes are added or deleted
const notes = useQuery ( refs . public . notesAndRandom . notes . list , {});
return (
< div >
< h2 > Total Notes: { notes ?. length ?? "Loading…" } </ h2 >
</ div >
);
};
Changes made in one tab, by another user, or by scheduled functions automatically propagate to all connected clients.
Error Handling
Query Errors
Queries handle errors gracefully by returning undefined:
const notes = useQuery ( refs . public . notes . list , {});
if ( notes === undefined ) {
// Either loading or error occurred
return < div > Loading… </ div > ;
}
Mutation and Action Errors
Handle promise rejections:
const insertNote = useMutation ( refs . public . notes . insert );
try {
await insertNote ({ text: "New note" });
} catch ( error ) {
console . error ( "Failed to insert note:" , error );
// Show error UI
}
Type Safety
Confect provides complete type inference:
// TypeScript infers everything from your schema
const notes = useQuery ( refs . public . notes . list , {});
// ^? Array<{ _id: string; text: string; ... }> | undefined
const insertNote = useMutation ( refs . public . notes . insert );
// ^? (args: { text: string }) => Promise<string>
// TypeScript catches errors
insertNote ({ text: 123 }); // Error: Type 'number' is not assignable to type 'string'
insertNote ({ title: "..." }); // Error: Object literal may only specify known properties
Best Practices
Use Type Inference Let TypeScript infer types from your function references rather than manually typing results
Handle Loading States Always check for undefined when using useQuery before rendering data
Catch Errors Wrap mutation and action calls in try-catch blocks for proper error handling
Keep Refs Generated Run npx @confect/cli codegen to regenerate refs after changing function specs
Next Steps
HTTP Client Learn how to call Confect APIs over HTTP
Server Functions Define queries, mutations, and actions
Database Work with the Confect database
Testing Test your Confect application