A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.
1import { useEffect, useState } from "react";
2import { usePdsEndpoint } from "./usePdsEndpoint";
3import { createAtprotoClient } from "../utils/atproto-client";
4
5/**
6 * Minimal profile fields returned by the Bluesky actor profile endpoint.
7 */
8export interface BlueskyProfileData {
9 /** Actor DID. */
10 did: string;
11 /** Actor handle. */
12 handle: string;
13 /** Display name configured by the actor. */
14 displayName?: string;
15 /** Profile description/bio. */
16 description?: string;
17 /** Avatar blob (CID reference). */
18 avatar?: string;
19 /** Banner image blob (CID reference). */
20 banner?: string;
21 /** Creation timestamp for the profile. */
22 createdAt?: string;
23}
24
25/**
26 * Fetches a Bluesky actor profile for a DID and exposes loading/error state.
27 *
28 * @param did - Actor DID whose profile should be retrieved.
29 * @returns {{ data: BlueskyProfileData | undefined; loading: boolean; error: Error | undefined }} Object exposing the profile payload, loading flag, and any error.
30 */
31export function useBlueskyProfile(did: string | undefined) {
32 const { endpoint } = usePdsEndpoint(did);
33 const [data, setData] = useState<BlueskyProfileData | undefined>();
34 const [loading, setLoading] = useState<boolean>(!!did);
35 const [error, setError] = useState<Error | undefined>();
36
37 useEffect(() => {
38 let cancelled = false;
39 async function run() {
40 if (!did || !endpoint) return;
41 setLoading(true);
42 try {
43 const { rpc } = await createAtprotoClient({
44 service: endpoint,
45 });
46 const client = rpc as unknown as {
47 get: (
48 nsid: string,
49 options: { params: { actor: string } },
50 ) => Promise<{ ok: boolean; data: unknown }>;
51 };
52 const res = await client.get("app.bsky.actor.getProfile", {
53 params: { actor: did },
54 });
55 if (!res.ok) throw new Error("Profile request failed");
56 if (!cancelled) setData(res.data as BlueskyProfileData);
57 } catch (e) {
58 if (!cancelled) setError(e as Error);
59 } finally {
60 if (!cancelled) setLoading(false);
61 }
62 }
63 run();
64 return () => {
65 cancelled = true;
66 };
67 }, [did, endpoint]);
68
69 return { data, loading, error };
70}