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.
This commit is contained in:
2026-08-08 12:59:32 +08:00
parent 7d1f05902d
commit 179efae99f
3 changed files with 49 additions and 28 deletions
+17 -10
View File
@@ -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 (
<Scene>
<mesh>
<boxGeometry args={[1.4, 2, 0.06]} />
<meshStandardMaterial
color={face || back ? '#ffffff' : '#52525b'}
map={face ?? back ?? undefined}
roughness={0.6}
/>
</mesh>
<CardMesh faceUrl={faceUrl} backUrl={backUrl} />
</Scene>
);
}
// 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 (
<mesh>
<boxGeometry args={[1.4, 2, 0.06]} />
<meshStandardMaterial
color={face || back ? '#ffffff' : '#52525b'}
map={face ?? back ?? undefined}
roughness={0.6}
/>
</mesh>
);
}
+16 -9
View File
@@ -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 (
<Scene>
<mesh rotation={[-Math.PI / 2, 0, 0]}>
<boxGeometry args={[1.6, 1.6, thickness]} />
<meshStandardMaterial
color={texture ? '#ffffff' : '#52525b'}
map={texture ?? undefined}
roughness={0.8}
/>
</mesh>
<TileMesh url={url} thickness={thickness} />
</Scene>
);
}
// 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 (
<mesh rotation={[-Math.PI / 2, 0, 0]}>
<boxGeometry args={[1.6, 1.6, thickness]} />
<meshStandardMaterial
color={texture ? '#ffffff' : '#52525b'}
map={texture ?? undefined}
roughness={0.8}
/>
</mesh>
);
}
@@ -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 (
<Scene>
<mesh rotation={[-Math.PI / 2, 0, 0]}>
<cylinderGeometry args={[0.9, 0.9, thickness, 48]} />
<meshStandardMaterial
color={texture ? '#ffffff' : '#52525b'}
map={texture ?? undefined}
roughness={0.8}
/>
</mesh>
<TokenMesh url={url} thickness={thickness} />
</Scene>
);
}
// 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 (
<mesh rotation={[-Math.PI / 2, 0, 0]}>
<cylinderGeometry args={[0.9, 0.9, thickness, 48]} />
<meshStandardMaterial
color={texture ? '#ffffff' : '#52525b'}
map={texture ?? undefined}
roughness={0.8}
/>
</mesh>
);
}