cRambo
cRambo
SSolidJS
Created by cRambo on 4/26/2025 in #support
SolidTable infinite loop with `createAsync` query, createMemo but not createSignal + createEffect?
I'm writing a SolidStart project, using SolidTable to display data from a backend DB. I'm using SolidStart query to fetch data and SolidStart actions to insert data. I'm running into an issue where if I supply the createAsync accessor of the query directly as the data parameter for the table - it hangs / hits out of memory errors when I insert a row using the action. I'm not 100% sure what causes the issue but it seems like it might be an infinite re-render of the table. If I keep everything the same but change the table data to be an empty array then it works no problems - so not an issue with the DB/query/action by themselves, it's only when its used as data for the table. I've tried some alternative approaches - createMemo of the createAsync accessor does not work - but strangely if I use createSignal and then update it with the value of the createAsync query in createEffect then it does work? Why createSignal/createEffect here work when createAsync does not? Does createAsync not have stable references? Please help me work this out - I'd really like to not use createEffect to make this work if I can avoid it. Thanks!
const getTests = query(async () => {
"use server";
const db = new Kysely<DB>({ dialect });
return await db.selectFrom("tests").selectAll().execute();
}, "getTests");

const addTest = action(async (formData: FormData) => {
"use server";
const db = new Kysely<DB>({ dialect });
const values: InsertableTest = {
// ...
};
await db.insertInto("tests").values(values).execute();
});

export const route = {
preload() {
getTests();
},
} satisfies RouteDefinition;

export default function TestView() {
const addTestAction = useAction(addTest);
const submission = useSubmission(addTest);

// 1. The `createAsync` accessor for the query DOES NOT work!
const data = createAsync<InsertableTest[]>(async () => await getTests());

// 2. Memoization of the `createAsync` query DOES NOT work!
const tableMemo = createMemo(() => data() ?? []);

// 3. Signal updated via `createEffect` DOES work?
const [tableData, setTableData] = createSignal<InsertableTest[]>([]);
createEffect(() => {
setTableData(data() ?? []);
});

return (
<div>
<DataTable columns={columns} data={data()} />
<Button
disabled={submission.pending}
onClick={() => {
const formData = new FormData();
// formData.append(...);
addTestAction(formData);
}}
>
Insert Test
</Button>
</div>
);
}

interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[] | undefined;
}

export function DataTable<TData, TValue>(props: DataTableProps<TData, TValue>) {
const table = createSolidTable({
get data() {
return props.data ?? [];
},
get columns() {
return props.columns;
},
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
table.setPageSize(20);
// ...
const getTests = query(async () => {
"use server";
const db = new Kysely<DB>({ dialect });
return await db.selectFrom("tests").selectAll().execute();
}, "getTests");

const addTest = action(async (formData: FormData) => {
"use server";
const db = new Kysely<DB>({ dialect });
const values: InsertableTest = {
// ...
};
await db.insertInto("tests").values(values).execute();
});

export const route = {
preload() {
getTests();
},
} satisfies RouteDefinition;

export default function TestView() {
const addTestAction = useAction(addTest);
const submission = useSubmission(addTest);

// 1. The `createAsync` accessor for the query DOES NOT work!
const data = createAsync<InsertableTest[]>(async () => await getTests());

// 2. Memoization of the `createAsync` query DOES NOT work!
const tableMemo = createMemo(() => data() ?? []);

// 3. Signal updated via `createEffect` DOES work?
const [tableData, setTableData] = createSignal<InsertableTest[]>([]);
createEffect(() => {
setTableData(data() ?? []);
});

return (
<div>
<DataTable columns={columns} data={data()} />
<Button
disabled={submission.pending}
onClick={() => {
const formData = new FormData();
// formData.append(...);
addTestAction(formData);
}}
>
Insert Test
</Button>
</div>
);
}

interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[] | undefined;
}

export function DataTable<TData, TValue>(props: DataTableProps<TData, TValue>) {
const table = createSolidTable({
get data() {
return props.data ?? [];
},
get columns() {
return props.columns;
},
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
table.setPageSize(20);
// ...
7 replies