From 179efae99f61d612feb12d55899d36ce5d35c359 Mon Sep 17 00:00:00 2001 From: hypercross Date: Sat, 8 Aug 2026 12:59:32 +0800 Subject: [PATCH] Fix R3F hooks used outside the Canvas Move useTexture calls in the tile, token, and card viewers into child components rendered inside the Canvas. R3F hooks must run within the Canvas store, so calling them at the viewer's top level threw a runtime error. --- .../web/src/components/viewers/CardViewer.tsx | 27 ++++++++++++------- .../web/src/components/viewers/TileViewer.tsx | 25 ++++++++++------- .../src/components/viewers/TokenViewer.tsx | 25 ++++++++++------- 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/apps/web/src/components/viewers/CardViewer.tsx b/apps/web/src/components/viewers/CardViewer.tsx index 61a8ca7..5fbff5b 100644 --- a/apps/web/src/components/viewers/CardViewer.tsx +++ b/apps/web/src/components/viewers/CardViewer.tsx @@ -12,19 +12,26 @@ export default function CardViewer({ object }: { object: TTSObject }) { const deck = object.CustomDeck ? Object.values(object.CustomDeck)[0] : undefined; const faceUrl = deck?.FaceURL; const backUrl = deck?.BackURL; - const face = faceUrl ? useTexture(assetUrl(faceUrl)) : null; - const back = backUrl ? useTexture(assetUrl(backUrl)) : null; return ( - - - - + ); +} + +// Rendered inside the Canvas so `useTexture` can access the R3F store. +function CardMesh({ faceUrl, backUrl }: { faceUrl?: string; backUrl?: string }) { + const face = faceUrl ? useTexture(assetUrl(faceUrl)) : null; + const back = backUrl ? useTexture(assetUrl(backUrl)) : null; + return ( + + + + + ); } \ No newline at end of file diff --git a/apps/web/src/components/viewers/TileViewer.tsx b/apps/web/src/components/viewers/TileViewer.tsx index a236dc6..5bb4191 100644 --- a/apps/web/src/components/viewers/TileViewer.tsx +++ b/apps/web/src/components/viewers/TileViewer.tsx @@ -9,19 +9,26 @@ import { assetUrl } from './assetUrl'; */ export default function TileViewer({ object }: { object: TTSObject }) { const url = object.CustomImage?.ImageURL ?? object.CustomImage?.ImageSecondaryURL; - const texture = url ? useTexture(assetUrl(url)) : null; const thickness = object.CustomImage?.CustomTile?.Thickness ?? 0.1; return ( - - - - + ); +} + +// Rendered inside the Canvas so `useTexture` can access the R3F store. +function TileMesh({ url, thickness }: { url?: string; thickness: number }) { + const texture = url ? useTexture(assetUrl(url)) : null; + return ( + + + + + ); } \ No newline at end of file diff --git a/apps/web/src/components/viewers/TokenViewer.tsx b/apps/web/src/components/viewers/TokenViewer.tsx index a1996ac..1bbb003 100644 --- a/apps/web/src/components/viewers/TokenViewer.tsx +++ b/apps/web/src/components/viewers/TokenViewer.tsx @@ -9,19 +9,26 @@ import { assetUrl } from './assetUrl'; */ export default function TokenViewer({ object }: { object: TTSObject }) { const url = object.CustomImage?.ImageURL ?? object.CustomImage?.ImageSecondaryURL; - const texture = url ? useTexture(assetUrl(url)) : null; const thickness = object.CustomImage?.CustomToken?.Thickness ?? 0.1; return ( - - - - + ); +} + +// Rendered inside the Canvas so `useTexture` can access the R3F store. +function TokenMesh({ url, thickness }: { url?: string; thickness: number }) { + const texture = url ? useTexture(assetUrl(url)) : null; + return ( + + + + + ); } \ No newline at end of file