Am I obliged to combine Suspense with Show?

I get the following error: Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'map') I understand the cause, but I'm surprised. I was expecting that the Suspense would delay the children until the data is available. Adding a Show solves the issue, but I'm wondering if I can handle this differently.
import { createQuery } from "@tanstack/solid-query";
import { Suspense } from "solid-js";

type Program = {
id: string;
name: string;
};

export default function List() {
const categories = createQuery<Program[]>(() => ({
queryKey: ["programs"],
queryFn: async () => {
return fetch("http://localhost:4000/api/programs")
.then((res) => res.json())
.then((data) => data.data);
},
}));

return (
<div>
<h1>Programs</h1>
<Suspense fallback={<p>Loading...</p>}>
{JSON.stringify(categories.data!.map((c) => c.name))}
</Suspense>
</div>
);
}
import { createQuery } from "@tanstack/solid-query";
import { Suspense } from "solid-js";

type Program = {
id: string;
name: string;
};

export default function List() {
const categories = createQuery<Program[]>(() => ({
queryKey: ["programs"],
queryFn: async () => {
return fetch("http://localhost:4000/api/programs")
.then((res) => res.json())
.then((data) => data.data);
},
}));

return (
<div>
<h1>Programs</h1>
<Suspense fallback={<p>Loading...</p>}>
{JSON.stringify(categories.data!.map((c) => c.name))}
</Suspense>
</div>
);
}
10 Replies
binajmen
binajmenOP8mo ago
In this simplified example, I could use ? instead of ! But in my current issue, I'm using solid-table:
import { A } from "@solidjs/router";
import { createQuery } from "@tanstack/solid-query";
import { createColumnHelper } from "@tanstack/solid-table";
import { Show, Suspense } from "solid-js";
import { DataTable } from "~/components/data-table";

type Program = {
id: string;
name: string;
};

export default function List() {
const categories = createQuery<Program[]>(() => ({
queryKey: ["programs"],
queryFn: async () => {
return fetch("http://localhost:4000/api/programs")
.then((res) => res.json())
.then((data) => data.data);
},
}));

const column = createColumnHelper<Program>();
const columns = [
column.accessor("id", {
header: "ID",
cell: (data) => data.getValue(),
}),
column.accessor("name", {
header: "Name",
cell: (data) => data.getValue(),
}),
column.display({
id: "actions",
cell: (data) => (
<A href={`/admin/programs/${data.row.original.id}`}>View</A>
),
}),
];

return (
<div>
<h1>Programs</h1>
<Suspense fallback={<p>Loading...</p>}>
<Show when={categories.isSuccess}>
<DataTable data={categories.data!} columns={columns} />
</Show>
</Suspense>
</div>
);
}`
import { A } from "@solidjs/router";
import { createQuery } from "@tanstack/solid-query";
import { createColumnHelper } from "@tanstack/solid-table";
import { Show, Suspense } from "solid-js";
import { DataTable } from "~/components/data-table";

type Program = {
id: string;
name: string;
};

export default function List() {
const categories = createQuery<Program[]>(() => ({
queryKey: ["programs"],
queryFn: async () => {
return fetch("http://localhost:4000/api/programs")
.then((res) => res.json())
.then((data) => data.data);
},
}));

const column = createColumnHelper<Program>();
const columns = [
column.accessor("id", {
header: "ID",
cell: (data) => data.getValue(),
}),
column.accessor("name", {
header: "Name",
cell: (data) => data.getValue(),
}),
column.display({
id: "actions",
cell: (data) => (
<A href={`/admin/programs/${data.row.original.id}`}>View</A>
),
}),
];

return (
<div>
<h1>Programs</h1>
<Suspense fallback={<p>Loading...</p>}>
<Show when={categories.isSuccess}>
<DataTable data={categories.data!} columns={columns} />
</Show>
</Suspense>
</div>
);
}`
peerreynders
peerreynders8mo ago
I've never used createQuery but if it's protocol is similar to createResource I'd expect .data to be undefined before the first promise settles. If that is the case you should be able to write:
<Show when={categories.data}>
{(data) => <DataTable data={data} columns={columns} />}
</Show>
<Show when={categories.data}>
{(data) => <DataTable data={data} columns={columns} />}
</Show>
thetarnav
thetarnav8mo ago
tip: set the initialValue of resource (or whatever the equivalent is for createQuery) and you won’t have to deal with data()?.stuff or <Show>
binajmen
binajmenOP8mo ago
So Suspense would not be the right tool in this case? Hmmm true Most probably tanner thought about that But I should still use Suspense even with an initial value, right?
thetarnav
thetarnav8mo ago
you’ll never see the Suspense fallback though (unless using ssr) or maybe even not then idk ah no you’ll not see the fallback if you do data.latest data() will trigger fallback
binajmen
binajmenOP8mo ago
Ok ill try that What I understand is that you choose Suspense or Show but not both
peerreynders
peerreynders8mo ago
I look at <Show> and <Switch>/ <Match> as a means of supplying rendering alternatives. <Suspense> is more about the paint holding during a transition (marked by unsettled async operations) while rendering the alternate, future branch; the fallback is only relevant in the absence of a transition which often is only the very first time the <Suspense> sub-tree renders.
Chrome for Developers
Paint Holding - reducing the flash of white on same-origin navigati...
A quick overview of paint holding. A Chrome feature for reducing the flash of white on same-origin navigations
binajmen
binajmenOP8mo ago
Hi @thetarnav, I liked the idea of providing an initial value. With solid-query I can either use initialData or placeholderData. I choose the latter as it doesn't pollute the cache:
Placeholder data allows a query to behave as if it already has data, similar to the initialData option, but the data is not persisted to the cache. (src: https://tanstack.com/query/v3/docs/framework/react/guides/placeholder-query-data#what-is-placeholder-data)
However I'm facing a different problem. When the data is loaded, the DataTable component is not updating the values accordingly I even tried with an intermediary signal (in comment) without success:
export function DataTable<TData extends { id: string }>(props: {
data: TableOptions<TData>["data"];
columns: TableOptions<TData>["columns"];
}) {
// const [data, setData] = createSignal(props.data);
const table = createSolidTable({
data: props.data,
// data: data(),
columns: props.columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getRowId: (row) => row.id,
});

// createEffect(() => setData(props.data));
createEffect(() => console.log(props.data));
export function DataTable<TData extends { id: string }>(props: {
data: TableOptions<TData>["data"];
columns: TableOptions<TData>["columns"];
}) {
// const [data, setData] = createSignal(props.data);
const table = createSolidTable({
data: props.data,
// data: data(),
columns: props.columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getRowId: (row) => row.id,
});

// createEffect(() => setData(props.data));
createEffect(() => console.log(props.data));
But this is probably a different question https://discord.com/channels/722131463138705510/1228246382582960199
thetarnav
thetarnav8mo ago
shouldn’t createSolidData take an accessor as one of the params? either it has some mechanism for handling reactive input or the whole thing needs to be wrapped in a memo
binajmen
binajmenOP8mo ago
Got my answer on reactivity there by accessor you mean get data() ? I was not aware of this 🙈
Want results from more Discord servers?
Add your server