+1
-1
package.json
+1
-1
package.json
+23
-160
src/App.tsx
+23
-160
src/App.tsx
···
6
6
useRef,
7
7
} from "react";
8
8
import { AtProtoProvider } from "../lib/providers/AtProtoProvider";
9
-
import { AtProtoRecord } from "../lib/core/AtProtoRecord";
9
+
10
10
import { TangledString } from "../lib/components/TangledString";
11
11
import { LeafletDocument } from "../lib/components/LeafletDocument";
12
12
import { BlueskyProfile } from "../lib/components/BlueskyProfile";
···
37
37
);
38
38
}`;
39
39
40
-
const customComponentSnippet = `import { useLatestRecord, useColorScheme, AtProtoRecord } from 'atproto-ui';
40
+
const prefetchedDataSnippet = `import { BlueskyPost, useLatestRecord } from 'atproto-ui';
41
41
import type { FeedPostRecord } from 'atproto-ui';
42
42
43
-
const LatestPostSummary: React.FC<{ did: string }> = ({ did }) => {
44
-
const scheme = useColorScheme('system');
45
-
const { rkey, loading, error } = useLatestRecord<FeedPostRecord>(did, 'app.bsky.feed.post');
43
+
const LatestPostWithPrefetch: React.FC<{ did: string }> = ({ did }) => {
44
+
// Fetch once with the hook
45
+
const { record, rkey, loading } = useLatestRecord<FeedPostRecord>(
46
+
did,
47
+
'app.bsky.feed.post'
48
+
);
46
49
47
50
if (loading) return <span>Loading…</span>;
48
-
if (error || !rkey) return <span>No post yet.</span>;
51
+
if (!record || !rkey) return <span>No posts yet.</span>;
49
52
50
-
return (
51
-
<AtProtoRecord<FeedPostRecord>
52
-
did={did}
53
-
collection="app.bsky.feed.post"
54
-
rkey={rkey}
55
-
renderer={({ record }) => (
56
-
<article data-color-scheme={scheme}>
57
-
<strong>{record?.text ?? 'Empty post'}</strong>
58
-
</article>
59
-
)}
60
-
/>
61
-
);
53
+
// Pass prefetched record—BlueskyPost won't re-fetch it
54
+
return <BlueskyPost did={did} rkey={rkey} record={record} />;
62
55
};`;
63
56
64
57
const codeBlockBase: React.CSSProperties = {
···
219
212
const basicCodeRef = useRef<HTMLElement | null>(null);
220
213
const customCodeRef = useRef<HTMLElement | null>(null);
221
214
222
-
// Latest Bluesky post
215
+
// Latest Bluesky post - fetch with record for prefetch demo
223
216
const {
217
+
record: latestPostRecord,
224
218
rkey: latestPostRkey,
225
219
loading: loadingLatestPost,
226
220
empty: noPosts,
227
221
error: latestPostError,
228
-
} = useLatestRecord<unknown>(did, BLUESKY_POST_COLLECTION);
222
+
} = useLatestRecord<FeedPostRecord>(did, BLUESKY_POST_COLLECTION);
229
223
230
224
const quoteSampleDid = "did:plc:ttdrpj45ibqunmfhdsb4zdwq";
231
225
const quoteSampleRkey = "3m2prlq6xxc2v";
···
323
317
<div style={columnStackStyle}>
324
318
<section style={panelStyle}>
325
319
<h3 style={sectionHeaderStyle}>
326
-
Latest Bluesky Post
320
+
Latest Post (Prefetched Data)
327
321
</h3>
322
+
<p style={{ fontSize: 12, color: mutedTextColor, margin: "0 0 8px" }}>
323
+
Using <code style={{ background: scheme === "dark" ? "#1e293b" : "#e2e8f0", padding: "2px 4px", borderRadius: 3 }}>useLatestRecord</code> to fetch once, then passing <code style={{ background: scheme === "dark" ? "#1e293b" : "#e2e8f0", padding: "2px 4px", borderRadius: 3 }}>record</code> prop—no re-fetch!
324
+
</p>
328
325
{loadingLatestPost && (
329
326
<div style={loadingBox}>
330
327
Loading latest post…
···
345
342
No posts found.
346
343
</div>
347
344
)}
348
-
{!loadingLatestPost && latestPostRkey && (
345
+
{!loadingLatestPost && latestPostRkey && latestPostRecord && (
349
346
<BlueskyPost
350
347
did={did}
351
348
rkey={latestPostRkey}
349
+
record={latestPostRecord}
352
350
colorScheme={colorSchemePreference}
353
351
/>
354
352
)}
···
392
390
</>
393
391
)}
394
392
<section style={{ ...panelStyle, marginTop: 32 }}>
395
-
<h3 style={sectionHeaderStyle}>Build your own component</h3>
393
+
<h3 style={sectionHeaderStyle}>Code Examples</h3>
396
394
<p style={{ color: mutedTextColor, margin: "4px 0 8px" }}>
397
395
Wrap your app with the provider once and drop the ready-made
398
396
components wherever you need them.
···
407
405
</code>
408
406
</pre>
409
407
<p style={{ color: mutedTextColor, margin: "16px 0 8px" }}>
410
-
Need to make your own component? Compose your own renderer
411
-
with the hooks and utilities that ship with the library.
408
+
Pass prefetched data to components to skip API calls—perfect for SSR or caching.
412
409
</p>
413
410
<pre style={codeBlockStyle}>
414
411
<code
···
416
413
className="language-tsx"
417
414
style={codeTextStyle}
418
415
>
419
-
{customComponentSnippet}
416
+
{prefetchedDataSnippet}
420
417
</code>
421
418
</pre>
422
-
{did && (
423
-
<div
424
-
style={{
425
-
marginTop: 16,
426
-
display: "flex",
427
-
flexDirection: "column",
428
-
gap: 12,
429
-
}}
430
-
>
431
-
<p style={{ color: mutedTextColor, margin: 0 }}>
432
-
Live example with your handle:
433
-
</p>
434
-
<LatestPostSummary
435
-
did={did}
436
-
handle={showHandle}
437
-
colorScheme={colorSchemePreference}
438
-
/>
439
-
</div>
440
-
)}
441
419
</section>
442
420
</div>
443
421
);
444
422
};
445
423
446
-
const LatestPostSummary: React.FC<{
447
-
did: string;
448
-
handle?: string;
449
-
colorScheme: ColorSchemePreference;
450
-
}> = ({ did, colorScheme }) => {
451
-
const { record, rkey, loading, error } = useLatestRecord<FeedPostRecord>(
452
-
did,
453
-
BLUESKY_POST_COLLECTION,
454
-
);
455
-
const scheme = useColorScheme(colorScheme);
456
-
const palette =
457
-
scheme === "dark"
458
-
? latestSummaryPalette.dark
459
-
: latestSummaryPalette.light;
460
424
461
-
if (loading) return <div style={palette.muted}>Loading summary…</div>;
462
-
if (error)
463
-
return <div style={palette.error}>Failed to load the latest post.</div>;
464
-
if (!rkey) return <div style={palette.muted}>No posts published yet.</div>;
465
-
466
-
const atProtoProps = record
467
-
? { record }
468
-
: { did, collection: "app.bsky.feed.post", rkey };
469
-
470
-
return (
471
-
<AtProtoRecord<FeedPostRecord>
472
-
{...atProtoProps}
473
-
renderer={({ record: resolvedRecord }) => (
474
-
<article data-color-scheme={scheme}>
475
-
<strong>{resolvedRecord?.text ?? "Empty post"}</strong>
476
-
</article>
477
-
)}
478
-
/>
479
-
);
480
-
};
481
425
482
426
const sectionHeaderStyle: React.CSSProperties = {
483
427
margin: "4px 0",
···
486
430
const loadingBox: React.CSSProperties = { padding: 8 };
487
431
const errorBox: React.CSSProperties = { padding: 8, color: "crimson" };
488
432
const infoBox: React.CSSProperties = { padding: 8, color: "#555" };
489
-
490
-
const latestSummaryPalette = {
491
-
light: {
492
-
card: {
493
-
border: "1px solid #e2e8f0",
494
-
background: "#ffffff",
495
-
borderRadius: 12,
496
-
padding: 12,
497
-
display: "flex",
498
-
flexDirection: "column",
499
-
gap: 8,
500
-
} satisfies React.CSSProperties,
501
-
header: {
502
-
display: "flex",
503
-
alignItems: "baseline",
504
-
justifyContent: "space-between",
505
-
gap: 12,
506
-
color: "#0f172a",
507
-
} satisfies React.CSSProperties,
508
-
time: {
509
-
fontSize: 12,
510
-
color: "#64748b",
511
-
} satisfies React.CSSProperties,
512
-
text: {
513
-
margin: 0,
514
-
color: "#1f2937",
515
-
whiteSpace: "pre-wrap",
516
-
} satisfies React.CSSProperties,
517
-
link: {
518
-
color: "#2563eb",
519
-
fontWeight: 600,
520
-
fontSize: 12,
521
-
textDecoration: "none",
522
-
} satisfies React.CSSProperties,
523
-
muted: {
524
-
color: "#64748b",
525
-
} satisfies React.CSSProperties,
526
-
error: {
527
-
color: "crimson",
528
-
} satisfies React.CSSProperties,
529
-
},
530
-
dark: {
531
-
card: {
532
-
border: "1px solid #1e293b",
533
-
background: "#0f172a",
534
-
borderRadius: 12,
535
-
padding: 12,
536
-
display: "flex",
537
-
flexDirection: "column",
538
-
gap: 8,
539
-
} satisfies React.CSSProperties,
540
-
header: {
541
-
display: "flex",
542
-
alignItems: "baseline",
543
-
justifyContent: "space-between",
544
-
gap: 12,
545
-
color: "#e2e8f0",
546
-
} satisfies React.CSSProperties,
547
-
time: {
548
-
fontSize: 12,
549
-
color: "#cbd5f5",
550
-
} satisfies React.CSSProperties,
551
-
text: {
552
-
margin: 0,
553
-
color: "#e2e8f0",
554
-
whiteSpace: "pre-wrap",
555
-
} satisfies React.CSSProperties,
556
-
link: {
557
-
color: "#38bdf8",
558
-
fontWeight: 600,
559
-
fontSize: 12,
560
-
textDecoration: "none",
561
-
} satisfies React.CSSProperties,
562
-
muted: {
563
-
color: "#94a3b8",
564
-
} satisfies React.CSSProperties,
565
-
error: {
566
-
color: "#f472b6",
567
-
} satisfies React.CSSProperties,
568
-
},
569
-
} as const;
570
433
571
434
export const App: React.FC = () => {
572
435
return (