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>
  );
}

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

  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'
    });