Skip to content

React Hooks

The @requence/socketql/client/react module provides the SocketQLProvider and Suspense-ready hooks built on top of URQL.

Wrap your app with SocketQLProvider to connect and provide the client to all hooks:

import { createClient } from '@requence/socketql/client'
import { SocketQLProvider } from '@requence/socketql/client/react'
const client = createClient({
auth: () => ({ token: getAccessToken() }),
})
function App() {
return (
<ErrorBoundary fallback={<LoginPage />}>
<SocketQLProvider client={client}>
<Main />
</SocketQLProvider>
</ErrorBoundary>
)
}

A thin wrapper around URQL’s useQuery that throws on errors (for error boundaries) and returns a [data, reexecute] tuple:

import { useQuery } from '@requence/socketql/client/react'
function UserList() {
const [data, reexecute] = useQuery({
query: UsersDocument,
})
return (
<>
<button onClick={() => reexecute({ requestPolicy: 'network-only' })}>
Refresh
</button>
<ul>
{data.users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
)
}

A wrapper around URQL’s useMutation with built-in Suspense support and cache invalidation:

import { useMutation } from '@requence/socketql/client/react'
function CreateUser() {
const [result, execute] = useMutation({
query: CreateUserDocument,
invalidate: 'Users', // invalidate cached "Users" query
waitOn: 'Users', // wait for the Users query to re-resolve
})
return (
<button onClick={() => execute({ name: 'Alice' })}>
Create User
</button>
)
}

waitOn also works with subscription documents — the mutation’s Suspense lock stays open until the first event from that subscription arrives after the mutation completes:

function DuplicateItem() {
const [, execute] = useMutation({
query: DuplicateItemDocument,
waitOn: ItemChangesDocument, // wait for the subscription to emit
waitOnTimeout: 3000,
})
return <button onClick={() => execute({ id: '123' })}>Duplicate</button>
}

For cases where you need to match a specific event by data — such as waiting for a subscription event whose id matches the one returned by the mutation — pass a predicate function. It receives the mutation result as its first argument, so you never need to close over mutable state:

function CreateItem() {
const [, execute] = useMutation({
query: CreateItemDocument,
waitOn: (mutationResult, result, name) =>
name === 'ItemCreated' &&
result.data?.itemCreated?.id === mutationResult.data?.createItem?.id,
waitOnTimeout: 5000,
})
return <button onClick={() => execute({ name: 'My New Item' })}>Create</button>
// Suspense lifts only after the ItemCreated subscription event
// whose id matches the newly created item.
}
OptionTypeDescription
queryDocumentInputThe mutation document
invalidatestring | DocumentNode | ArrayQueries to invalidate after mutation
suspensebooleanWhether to trigger Suspense (default: true)
waitOnstring | DocumentNode | Array | FunctionWait for a query/subscription to emit, or resolve when a predicate returns true
waitOnTimeoutnumberTimeout in milliseconds to resolve the wait lock early

A thin wrapper around URQL’s useSubscription that throws on errors (for error boundaries) and returns a [data, execute] tuple:

import { useSubscription } from '@requence/socketql/client/react'
function ChatMessages() {
const [message] = useSubscription({
query: OnNewMessageDocument,
variables: { channelId: 'general' },
})
if (!message) return <p>Waiting for messages…</p>
return <p>Latest: {message.text}</p>
}

Pass a handler function to accumulate data across subscription events — useful for building lists from a stream:

function MessageList() {
const [messages] = useSubscription(
{ query: OnNewMessageDocument },
(prev = [], event) => [...prev, event],
)
return (
<ul>
{messages?.map(msg => (
<li key={msg.id}>{msg.text}</li>
))}
</ul>
)
}