convex-solidjs
v0.0.3
Published
Convex SolidJS Client
Readme
convex-solidjs
Type-safe, reactive Convex client for SolidJS with real-time subscriptions and fine-grained reactivity.
Convex is the typesafe backend-as-a-service with realtime updates, server functions, crons and scheduled jobs, file storage, vector search, and more.
convex-solidjs provides a native SolidJS integration with automatic reactivity, type safety, and real-time updates.
Installation
npm install convex convex-solidjs
# or
pnpm add convex convex-solidjsRun npx convex init to get started with Convex.
Features
- 🎯 Zero
anytypes - Fully type-safe with excellent TypeScript support - ⚡ SolidJS Native - Built with SolidJS primitives (createResource, createSignal, createMemo)
- 🔄 Reactive Arguments - Pass signals or static values, the choice is yours
- 🔄 Real-time Updates - Automatic subscription to Convex queries with live data synchronization
- 📦 Small Bundle - Minimal overhead on top of Convex client (~5KB gzipped)
- 🎨 Clean API - Intuitive and easy to use
- 💪 SSR Support - Server-side rendering ready with
initialDataoption - 🔀 Stale-While-Revalidate - Keep showing previous data while loading new results
Quick Start
1. Install Dependencies
npm install convex convex-solidjs
# or
pnpm add convex convex-solidjs2. Initialize Convex
npx convex init3. Setup Your App
Wrap your app with ConvexProvider:
import { setupConvex, ConvexProvider } from 'convex-solidjs'
import { render } from 'solid-js/web'
import App from './App'
const client = setupConvex(import.meta.env.VITE_CONVEX_URL)
render(
() => (
<ConvexProvider client={client}>
<App />
</ConvexProvider>
),
document.getElementById('root')!
)Core Concepts
Queries with Reactive Arguments
Queries automatically re-run when their arguments change:
import { useQuery } from 'convex-solidjs'
import { api } from '../convex/_generated/api'
import { createSignal, For, Show } from 'solid-js'
function Messages() {
const [channel, setChannel] = createSignal('general')
// Query re-runs automatically when channel changes
const messages = useQuery(
api.messages.list,
() => ({ channel: channel() }), // Reactive arguments!
{ keepPreviousData: true } // Show old data while loading new
)
return (
<div>
<select onChange={e => setChannel(e.target.value)}>
<option value="general">General</option>
<option value="random">Random</option>
</select>
<Show when={!messages.isLoading()} fallback={<div>Loading...</div>}>
<Show when={messages.error()}>
<div>Error: {messages.error()?.message}</div>
</Show>
<For each={messages.data()}>{message => <div>{message.text}</div>}</For>
<Show when={messages.isStale()}>
<span>Updating...</span>
</Show>
</Show>
</div>
)
}Mutations with Type-Safe Arguments
Use useMutation() for type-safe mutations with loading states:
import { useMutation } from 'convex-solidjs'
import { api } from '../convex/_generated/api'
function MessageForm() {
const [text, setText] = createSignal('')
const sendMessage = useMutation(api.messages.send)
const handleSubmit = async (e: Event) => {
e.preventDefault()
try {
await sendMessage.mutate({
text: text(),
channel: 'general',
})
setText('')
} catch (error) {
console.error('Failed to send:', error)
}
}
return (
<form onSubmit={handleSubmit}>
<input
value={text()}
onInput={e => setText(e.currentTarget.value)}
disabled={sendMessage.isLoading()}
/>
<button type="submit" disabled={!text() || sendMessage.isLoading()}>
{sendMessage.isLoading() ? 'Sending...' : 'Send'}
</button>
<Show when={sendMessage.error()}>
<div>Error: {sendMessage.error()?.message}</div>
</Show>
</form>
)
}Actions
Use useAction() for Convex actions (same API as mutations):
const generateResponse = useAction(api.ai.generate)
const handleGenerate = async () => {
const response = await generateResponse.mutate({
prompt: 'Hello, AI!',
})
console.log(response)
}API Reference
setupConvex(url, options?)
Creates a Convex client instance.
const client = setupConvex('https://your-app.convex.cloud', {
// Optional: disable in SSR
disabled: isServer,
})useQuery(query, args, options?)
Subscribe to a Convex query with reactive arguments.
const query = useQuery(
api.messages.list,
() => ({ channel: currentChannel() }), // Can be a function or static value
{
enabled: isLoggedIn(), // Conditional fetching
keepPreviousData: true, // Show stale data while loading
initialData: [], // SSR/Hydration support
},
)
// Returns:
query.data() // T | undefined
query.error() // Error | undefined
query.isLoading() // boolean
query.isStale() // boolean
query.refetch() // () => voiduseMutation(mutation)
Execute Convex mutations with loading states.
const mutation = useMutation(api.messages.send)
// Call it
await mutation.mutate({ text: 'Hello' })
// or
await mutation.mutateAsync({ text: 'Hello' })
// State
mutation.data() // Result of last successful mutation
mutation.error() // Error from last failed mutation
mutation.isLoading() // Is mutation in progress
mutation.reset() // Clear data and erroruseAction(action)
Execute Convex actions (same API as mutations).
const action = useAction(api.ai.generate)
await action.mutate({ prompt: '...' })useConvexClient()
Get the raw Convex client for advanced use cases:
const client = useConvexClient()
// Set auth
await client.setAuth(token)
// Call functions directly
const result = await client.mutation(api.foo.bar, args)Key Design Decisions
- Reactive Arguments: Both args and options can be static values or accessor functions
- Resource-Based: Uses SolidJS's
createResourcefor optimal performance - Type Safety: Full TypeScript inference with zero type assertions
- Clean Returns: No property getters, just simple accessor functions
Advanced Usage
Conditional Queries
Control when queries run with the enabled option:
const user = useQuery(
api.users.current,
{},
() => ({ enabled: isAuthenticated() }) // Only fetch when authenticated
)SSR and Hydration
Support server-side rendering with initial data:
const messages = useQuery(
api.messages.list,
{ channel: 'general' },
{
initialData: serverData, // Data from SSR
keepPreviousData: true
}
)Direct Client Access
Access the Convex client directly for advanced scenarios:
import { useConvexClient } from 'convex-solidjs'
function AuthButton() {
const client = useConvexClient()
const handleLogin = async () => {
await client.setAuth(token)
}
return <button onClick={handleLogin}>Login</button>
}Differences from Other Frameworks
vs React (convex/react)
- No hooks rules: Use anywhere in SolidJS components
- Fine-grained reactivity: Only re-runs what changes
- Accessor pattern: Returns functions, not values
- Reactive arguments: Pass signals directly for automatic updates
vs Svelte (convex-svelte-ssr)
- Signal-based state: Uses SolidJS signals instead of Svelte's runes
- Resource integration: Built on
createResourcefor optimal async handling - Batch updates: Leverages SolidJS's batching for performance
Example Application
Check out the /dev folder for a complete chat application showcasing:
- User and channel creation
- Real-time message updates
- AI response generation with GPT-4
- Optimistic updates
- Error handling
- Loading states
Run the demo:
cd dev
pnpm install
pnpm run devDeploying
In production build pipelines use the build command:
npx convex deploy --cmd-url-env-var-name VITE_CONVEX_URL --cmd 'npm run build'to build your SolidJS app and deploy Convex functions.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Development Setup
- Clone the repository
- Install dependencies:
pnpm install - Build the library:
pnpm run build - Run tests:
pnpm test(if applicable) - Run the demo:
cd dev && pnpm run dev
Project Structure
convex-solidjs/
├── src/
│ └── index.tsx # Main library code
├── dev/ # Demo application
│ ├── convex/ # Convex backend
│ └── App.tsx # Demo UI
├── package.json
└── README.mdSupport
License
MIT © Frank
