EGamalielGZ
EGamalielGZ•13mo ago

Is there any way to define a signal inside a route component?

Is there any way to define a signal inside a route component? I am trying to do it but an error raises Error:
An error occurred during route handling or page rendering.

Error: Hook can only be invoked from render methods.
at Object.i.__h (https://esm.sh/stable/preact@10.15.1/denonext/debug.js:39:147)
at l (https://esm.sh/stable/preact@10.15.1/denonext/hooks.js:2:168)
at T (https://esm.sh/stable/preact@10.15.1/denonext/hooks.js:2:1458)
at E (https://esm.sh/v114/@preact/signals@1.1.3/X-ZS8q/deno/signals.mjs:2:2347)
at DinosaurPage (file:///C:/Users/user/projects/RoomHome/routes/dinosaur.tsx:32:21)
at eventLoopTick (ext:core/01_core.js:183:11)
at async render (https://deno.land/x/fresh@1.4.3/src/server/render.ts:216:19)
at async https://deno.land/x/fresh@1.4.3/src/server/context.ts:794:24
at async handler (https://deno.land/x/fresh@1.4.3/src/server/context.ts:508:14)
at async ext:deno_http/00_serve.js:442:22
An error occurred during route handling or page rendering.

Error: Hook can only be invoked from render methods.
at Object.i.__h (https://esm.sh/stable/preact@10.15.1/denonext/debug.js:39:147)
at l (https://esm.sh/stable/preact@10.15.1/denonext/hooks.js:2:168)
at T (https://esm.sh/stable/preact@10.15.1/denonext/hooks.js:2:1458)
at E (https://esm.sh/v114/@preact/signals@1.1.3/X-ZS8q/deno/signals.mjs:2:2347)
at DinosaurPage (file:///C:/Users/user/projects/RoomHome/routes/dinosaur.tsx:32:21)
at eventLoopTick (ext:core/01_core.js:183:11)
at async render (https://deno.land/x/fresh@1.4.3/src/server/render.ts:216:19)
at async https://deno.land/x/fresh@1.4.3/src/server/context.ts:794:24
at async handler (https://deno.land/x/fresh@1.4.3/src/server/context.ts:508:14)
at async ext:deno_http/00_serve.js:442:22
Code:
export default async function DinosaurPage(_req: Request, ctx: RouteContext) {
const res = await fetch(new URL(`${ctx.url.origin}/api/dinosaur`));

if (!(res.status === 200)) {
return (
<div>
Error
</div>
);
}

const { data } = await res.json() as { data: Dinosaur[] };

const dinosaurs = useSignal<Dinosaur[]>(data);
const total = useComputed(() => dinosaurs.value.length);

return (
<>
<div class="p-4">
<h1 class="text-4xl pb-4 font-semibold">
Mis dinosaurios favoritos :D - Total: {total}
</h1>
<div class="flex flex-col gap-4">
{dinosaurs.value.map((dinosaur: Dinosaur, index: number) => (
<DinosaurCard dinosaur={dinosaur} />
))}
</div>
</div>
</>
);
}
export default async function DinosaurPage(_req: Request, ctx: RouteContext) {
const res = await fetch(new URL(`${ctx.url.origin}/api/dinosaur`));

if (!(res.status === 200)) {
return (
<div>
Error
</div>
);
}

const { data } = await res.json() as { data: Dinosaur[] };

const dinosaurs = useSignal<Dinosaur[]>(data);
const total = useComputed(() => dinosaurs.value.length);

return (
<>
<div class="p-4">
<h1 class="text-4xl pb-4 font-semibold">
Mis dinosaurios favoritos :D - Total: {total}
</h1>
<div class="flex flex-col gap-4">
{dinosaurs.value.map((dinosaur: Dinosaur, index: number) => (
<DinosaurCard dinosaur={dinosaur} />
))}
</div>
</div>
</>
);
}
3 Replies
Deno AI Helper
Deno AI Helper•13mo ago
Based on the documentation, it seems like you're trying to use Preact Signals inside an async route component. However, the error message you're seeing suggests that the useSignal hook is being called outside of a Preact component's render method. In Preact (and React), hooks like useSignal must be called during the rendering of a component. They cannot be called in an asynchronous function or outside of a component's render method. Here's an example of how to use useSignal inside a component, as per the documentation:
import { useSignal } from "@preact/signals";

interface CounterProps {
start: number;
}

export default function Counter(props: CounterProps) {
const count = useSignal(props.start);
return (
<div>
<p>{count}</p>
<button onClick={() => count.value--}>-1</button>
<button onClick={() => count.value++}>+1</button>
</div>
);
}
import { useSignal } from "@preact/signals";

interface CounterProps {
start: number;
}

export default function Counter(props: CounterProps) {
const count = useSignal(props.start);
return (
<div>
<p>{count}</p>
<button onClick={() => count.value--}>-1</button>
<button onClick={() => count.value++}>+1</button>
</div>
);
}
In your case, you're trying to use useSignal inside an async function, which is not allowed. You should move the useSignal calls inside the component's render method. If you need to fetch data asynchronously, you can do so outside of the component and pass the data as a prop to the component. Alternatively, you can use an effect hook (like useEffect in React) to fetch data after the component has been rendered, and then update the state with the fetched data. Please note that this advice is based on general React/Preact principles and not explicitly stated in the documentation.
marvinh.
marvinh.•13mo ago
@EGamalielGZ Async routes are not truly Preact components, but rather functions that happen to return JSX. Because of that the component context that hooks require is not set up and the error is thrown. To address this use the signal() function instead of useSignal()
EGamalielGZ
EGamalielGZ•13mo ago
Thanks for the help 😄