Client Hooks
React hooks for accessing synchronized data.
useModel
Get a single model instance by ID.
Signature
function useModel<T extends Model>(
type: string,
id: string | null | undefined
): T | nullParameters
| Parameter | Type | Description |
|---|---|---|
type | string | Model type name |
id | string | null | undefined | Model ID (can be null) |
Returns
Model instance or null if id is null/undefined or model not found.
Example
const IssueDetail = observer(({ issueId }) => {
const issue = useModel<Issue>('issue', issueId)
if (!issue) return <div>Issue not found</div>
return <div>{issue.title}</div>
})Reactivity
Re-renders when:
- Model is updated in the pool
- Model is deleted from the pool
useCollectionModels
Get a collection of models, optionally filtered.
Signature
function useCollectionModels<T extends Model>(
type: string,
filter?: (model: T) => boolean
): T[]Parameters
| Parameter | Type | Description |
|---|---|---|
type | string | Model type name |
filter | (model: T) => boolean | Optional filter function |
Returns
Array of model instances.
Examples
// All tasks
const TaskList = observer(() => {
const tasks = useCollectionModels<Task>('task')
return tasks.map(task => (
<TaskRow key={task.id} task={task} />
))
})
// Filtered tasks
const ActiveTasks = observer(() => {
const tasks = useCollectionModels<Task>('task',
task => !task.done
)
return tasks.map(task => (
<TaskRow key={task.id} task={task} />
))
})
// Tasks for specific team
const TeamTasks = observer(({ teamId }) => {
const tasks = useCollectionModels<Task>('task',
task => task.teamId === teamId
)
return tasks.map(task => (
<TaskRow key={task.id} task={task} />
))
})Reactivity
Re-renders when:
- Any model of this type is added
- Any model of this type is updated
- Any model of this type is deleted
Type Hydration
useCollectionModels automatically hydrates the type from storage on mount:
// First render: Loads type from storage
const tasks = useCollectionModels<Task>('task')
// Subsequent renders: Uses cached data from ObjectPooluseStore
Access the store instance for mutations.
Signature
function useStore(): StoreReturns
Store instance with mutation methods.
Example
const TaskEditor = observer(({ task }) => {
const store = useStore()
const handleSave = async () => {
await store.save('task', task.id, {
title: task.title,
done: task.done
})
}
const handleDelete = async () => {
await store.remove('task', task.id)
}
return (
<div>
<input value={task.title} onChange={e => task.title = e.target.value} />
<button onClick={handleSave}>Save</button>
<button onClick={handleDelete}>Delete</button>
</div>
)
})useConnectionState
Monitor sync connection state.
Signature
function useConnectionState(): ConnectionStateReturns
interface ConnectionState {
isOnline: boolean
isSyncing: boolean
}Example
const SyncIndicator = observer(() => {
const { isOnline, isSyncing } = useConnectionState()
if (!isOnline) return <Badge variant='warning'>Offline</Badge>
if (isSyncing) return <Badge variant='info'>Syncing...</Badge>
return <Badge variant='success'>Synced ✓</Badge>
})observer
MobX observer HOC - re-exported from MobX for convenience.
Signature
function observer<T>(component: React.FC<T>): React.FC<T>Example
import { observer, useModel } from '@gluonic/client'
const TaskRow = observer(({ taskId }) => {
const task = useModel<Task>('task', taskId)
if (!task) return null
// Automatically re-renders when task updates
return <div>{task.title}</div>
})Rules
All components that read model data MUST be wrapped with observer():
// ✅ Correct
const TaskRow = observer(({ task }) => {
return <div>{task.title}</div> // Will update
})
// ❌ Wrong
const TaskRow = ({ task }) => {
return <div>{task.title}</div> // Won't update!
}useIdentityMap
Access the IdentityMap instance (advanced use only).
Signature
function useIdentityMap(): IdentityMapReturns
IdentityMap instance with identity mapping methods.
Example
const AdvancedComponent = () => {
const bridge = useIdentityMap()
// Access model directly
const issue = bridge.getModel<Issue>('issue', issueId)
// Query by index
const teamIssues = bridge.queryByIndex<Issue>('issue', 'teamId', teamId)
return <div>...</div>
}Advanced: Most apps should use useModel and useCollectionModels instead. Only use useIdentityMap for advanced patterns.
Hook Patterns
Combining Hooks
const IssueDetail = observer(({ issueId }) => {
const issue = useModel<Issue>('issue', issueId)
const comments = useCollectionModels<Comment>('comment',
c => c.issueId === issueId
)
const store = useStore()
const { isOnline } = useConnectionState()
if (!issue) return <div>Not found</div>
return (
<div>
<h1>{issue.title}</h1>
<CommentList comments={comments} />
{isOnline && <button onClick={() => store.save(...)}>Save</button>}
</div>
)
})Custom Hooks
// Custom hook wrapping Gluonic hooks
function useTeamIssues(teamId: string) {
const issues = useCollectionModels<Issue>('issue',
issue => issue.teamId === teamId
)
const openIssues = issues.filter(i => !i.done)
const closedIssues = issues.filter(i => i.done)
return { issues, openIssues, closedIssues }
}
// Usage
const TeamBoard = observer(({ teamId }) => {
const { openIssues, closedIssues } = useTeamIssues(teamId)
return (
<div>
<Column>{openIssues.map(...)}</Column>
<Column>{closedIssues.map(...)}</Column>
</div>
)
})See Also
- Model - Base model class
- Store - Store methods
- Client Hooks Guide - Complete guide
- Reactive Models - How reactivity works