Developer Guides
Hooking up models to the UI

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 instance
  • error: Any connection errors
  • reload: Function to retry the connection
  • mutate: 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>
  );
}

useModel

Provides a convenient way to query and subscribe to AD4M models with pagination support.

import { useModel } from '@coasys/ad4m-react-hooks';
import { Todo } from './models/Todo';
 
function TodoList({ perspective }) {
  const { entries, loading, error, totalCount, loadMore } = useModel<Todo>({
    perspective,
    model: Todo,
    query: { /* optional query parameters */ },
    pageSize: 10, // Optional - enables pagination
    preserveReferences: true // Optional - improves rendering performance
  });
 
  if (error) return <div>Error: {error}</div>;
  if (loading && entries.length === 0) return <div>Loading...</div>;
 
  return (
    <div>
      <h2>Todos ({totalCount})</h2>
      {entries.map(todo => (
        <div key={todo.baseExpression}>
          <h3>{todo.title}</h3>
          <p>{todo.description}</p>
        </div>
      ))}
 
      {entries.length < totalCount && (
        <button onClick={loadMore} disabled={loading}>
          {loading ? 'Loading...' : 'Load More'}
        </button>
      )}
    </div>
  );
}

Best Practices

  1. Always handle loading and error states

    if (error) return <ErrorComponent error={error} />;
    if (!data) return <LoadingComponent />;
  2. Clean up subscriptions

    useEffect(() => {
      const callback = (perspective, link) => { /* ... */ };
      onLinkAdded(callback);
      return () => onLinkRemoved(callback);
    }, []);
  3. 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
    });
  4. Leverage caching

    // The hooks handle caching automatically
    const { data: perspective1 } = usePerspective(client, uuid1);
    const { data: perspective2 } = usePerspective(client, uuid2);
  5. Use TypeScript for better type safety

    interface TodoSubject {
      id: string;
      title: string;
      description: string;
    }
     
    const { entries } = useSubjects<TodoSubject>({
      perspective,
      subject: 'Todo'
    });