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>
);
}
useSubject
Manages state for a single subject instance within a perspective.
import { useSubject } from '@coasys/ad4m-react-hooks';
function TodoItem({ perspective, id }) {
const { entry, error, repo, reload } = useSubject({
id,
perspective,
subject: 'Todo'
});
if (error) return <div>Error: {error}</div>;
if (!entry) return <div>Loading...</div>;
return (
<div>
<h3>{entry.title}</h3>
<p>{entry.description}</p>
<button onClick={() => repo.remove(id)}>Delete</button>
<button onClick={reload}>Refresh</button>
</div>
);
}
useSubjects
Manages state for multiple subject instances with filtering and pagination.
import { useSubjects } from '@coasys/ad4m-react-hooks';
function TodoList({ perspective }) {
const { entries, error, repo, isLoading, isMore, setQuery } = useSubjects({
perspective,
subject: 'Todo',
source: 'ad4m://self',
query: {
page: 1,
size: 10,
infinite: false
}
});
if (error) return <div>Error: {error}</div>;
if (isLoading) return <div>Loading...</div>;
return (
<div>
{entries.map(todo => (
<div key={todo.id}>
<h3>{todo.title}</h3>
<p>{todo.description}</p>
</div>
))}
{isMore && (
<button onClick={() => setQuery(prev => ({ ...prev, page: prev.page + 1 }))}>
Load More
</button>
)}
</div>
);
}
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
interface TodoSubject { id: string; title: string; description: string; } const { entries } = useSubjects<TodoSubject>({ perspective, subject: 'Todo' });