AD4M React Hooks
AD4M provides a set of React hooks to easily work with core AD4M concepts like Agents, Perspectives, and Subjects. These hooks handle state management, caching, and real-time updates automatically.
useClient
The most fundamental hook that provides access to the AD4M client instance.
import { useClient } from '@coasys/ad4m-react-hooks';
function MyComponent() {
const { client, error, reload } = useClient();
if (error) {
return <div>Error connecting to AD4M: {error}</div>;
}
if (!client) {
return <div>Connecting to AD4M...</div>;
}
return <div>Connected to AD4M</div>;
}The hook returns:
client: The AD4M client instanceerror: Any connection errorsreload: Function to retry the connectionmutate: Function to update the cached client instance
useAgent
Fetches and caches agent data by DID, with support for profile formatting.
import { useAgent } from '@coasys/ad4m-react-hooks';
function AgentProfile({ agentClient, did }) {
const { agent, profile, error } = useAgent({
client: agentClient,
did,
formatter: (links) => ({
name: links.find(l => l.data.predicate === 'name')?.data.target,
bio: links.find(l => l.data.predicate === 'bio')?.data.target
})
});
if (error) return <div>Error: {error}</div>;
if (!agent) return <div>Loading...</div>;
return (
<div>
<h2>{profile?.name}</h2>
<p>{profile?.bio}</p>
<p>DID: {agent.did}</p>
</div>
);
}useMe
Provides access to the current agent's data and status.
import { useMe } from '@coasys/ad4m-react-hooks';
function MyProfile({ agentClient }) {
const { me, status, profile, error } = useMe(agentClient, (links) => ({
name: links.find(l => l.data.predicate === 'name')?.data.target,
bio: links.find(l => l.data.predicate === 'bio')?.data.target
}));
if (error) return <div>Error: {error}</div>;
if (!me) return <div>Loading...</div>;
return (
<div>
<h2>{profile?.name}</h2>
<p>{profile?.bio}</p>
<p>Status: {status.isUnlocked ? 'Unlocked' : 'Locked'}</p>
</div>
);
}usePerspective
Fetches and subscribes to a single perspective by UUID.
import { usePerspective } from '@coasys/ad4m-react-hooks';
function Perspective({ client, uuid }) {
const { data } = usePerspective(client, uuid);
const { perspective, synced } = data;
if (!perspective) return <div>Loading...</div>;
return (
<div>
<h2>{perspective.name}</h2>
<p>Synced: {synced ? 'Yes' : 'No'}</p>
<p>UUID: {perspective.uuid}</p>
</div>
);
}usePerspectives
Provides access to all perspectives and handles real-time updates.
import { usePerspectives } from '@coasys/ad4m-react-hooks';
function PerspectivesList({ client }) {
const { perspectives, neighbourhoods, onLinkAdded, onLinkRemoved } = usePerspectives(client);
useEffect(() => {
const handleNewLink = (perspective, link) => {
console.log(`New link in ${perspective.name}:`, link);
};
onLinkAdded(handleNewLink);
return () => onLinkRemoved(handleNewLink);
}, []);
return (
<div>
<h2>All Perspectives</h2>
<ul>
{Object.values(perspectives).map(p => (
<li key={p.uuid}>{p.name}</li>
))}
</ul>
<h2>Neighbourhoods</h2>
<ul>
{Object.values(neighbourhoods).map(p => (
<li key={p.uuid}>{p.name}</li>
))}
</ul>
</div>
);
}useLiveQuery
Provides reactive, live-updating access to AD4M model data with support for collections, single instances, pagination, and parent-scoped queries. Replaces the previous useModel hook.
Collection Mode (default)
import { useLiveQuery } from '@coasys/ad4m-react-hooks';
import { Todo } from './models/Todo';
function TodoList({ perspective }) {
const { data, loading, error, totalCount, loadMore } = useLiveQuery(
Todo,
perspective,
{
query: { where: { status: "active" } },
pageSize: 10,
}
);
if (error) return <div>Error: {error}</div>;
if (loading && data.length === 0) return <div>Loading...</div>;
return (
<div>
<h2>Todos ({totalCount})</h2>
{data.map(todo => (
<div key={todo.id}>
<h3>{todo.title}</h3>
<p>{todo.description}</p>
</div>
))}
{data.length < totalCount && (
<button onClick={loadMore} disabled={loading}>
{loading ? 'Loading...' : 'Load More'}
</button>
)}
</div>
);
}Single-Instance Mode
When you provide id in options, the hook returns a single item instead of an array:
function TodoDetail({ perspective, todoId }) {
const { data: todo, loading, error } = useLiveQuery(
Todo,
perspective,
{ id: todoId }
);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!todo) return <div>Not found</div>;
return <h2>{todo.title}</h2>;
}Parent-Scoped Queries
Filter results to children of a specific parent:
function RecipeComments({ perspective, recipe }) {
const { data: comments } = useLiveQuery(
Comment,
perspective,
{
parent: { model: Recipe, id: recipe.id }
}
);
return (
<ul>
{comments.map(c => <li key={c.id}>{c.body}</li>)}
</ul>
);
}Return Types
Collection mode (LiveCollectionResult<T>):
data: T[]— Array of model instancesloading: boolean— Whether a query is in progresserror: string— Error message if anytotalCount: number— Total matching items (for pagination)loadMore: () => void— Load next page
Single-instance mode (LiveInstanceResult<T>):
data: T | null— The model instance or nullloading: booleanerror: string
Vue Usage
The Vue hook has the same API but returns Vue Ref-wrapped values and accepts ComputedRef<PerspectiveProxy> for the perspective parameter:
import { useLiveQuery } from '@coasys/ad4m-vue-hooks';
const { data, loading, error, totalCount, loadMore } = useLiveQuery(
Todo,
perspective, // Can be a ComputedRef
{ pageSize: 10 }
);
// data.value is T[] (collection) or T | null (single-instance)The Vue hook automatically re-subscribes when the perspective ref changes, and cleans up subscriptions on unmount.
Important Notes
- Registration: Call
<ModelClass>.register(perspective)once at app startup (e.g.,await Recipe.register(perspective)) — the hook does not register models automatically. Use the concrete generated class, not the@Modeldecorator. - Cleanup: Subscriptions are automatically disposed on component unmount.
- String model support: You can pass a model class name string instead of a class for dynamic/generic use cases.
Best Practices
-
Always handle loading and error states
if (error) return <ErrorComponent error={error} />; if (!data) return <LoadingComponent />; -
Clean up subscriptions
useEffect(() => { const callback = (perspective, link) => { /* ... */ }; onLinkAdded(callback); return () => onLinkRemoved(callback); }, []); -
Use formatters for consistent data structure
const formatter = (links) => ({ name: links.find(l => l.data.predicate === 'name')?.data.target, bio: links.find(l => l.data.predicate === 'bio')?.data.target }); -
Leverage caching
// The hooks handle caching automatically const { data: perspective1 } = usePerspective(client, uuid1); const { data: perspective2 } = usePerspective(client, uuid2); -
Use TypeScript for better type safety
// useLiveQuery is fully typed — generics are inferred from the model class const { data } = useLiveQuery(Todo, perspective); // data is Todo[] with full autocomplete