Cómo usar
- Copia el comando y revisa el paquete antes de instalarlo en tu entorno de agente.
- Consulta las herramientas, credenciales y entornos necesarios en el documento original.
- Describe las entradas y el resultado deseado; contrasta la salida con las fuentes.
npx skills add https://github.com/taiki-kuraishi/skill --skill kstack-tanstack-dbAntes de empezar
- La instalación no incluye suscripción al modelo, cuota API ni permisos sobre tus archivos.
- Las estrellas e instalaciones reflejan actividad, no una prueba independiente de calidad o seguridad.
Contenido del repositorio de origen con títulos, ejemplos, código, tablas, enlaces e imágenes preservados.
kstack-tanstack-db
TanStack DB の live query を書くときの規約。理由を理解した上で適用すること。useLiveQuery を置く場所(view の hook の中)は kstack-react の SKILL を参照。
無効化は query ごと undefined にし、where には常に完全な式を渡す
入力が揃わないときは、先頭のガード句で query ごと undefined を返して無効化する。条件に応じて .where() を足し引きして query の形を変えず、.where() には常に完全な式を渡す。任意の条件も、どの分岐でも完全な式を返し、and() で合成する。
// Bad: where の中で undefined を返している。undefined は式ではないので InvalidWhereExpressionError で throw する
useLiveQuery({
query: (q) =>
q.from({ todo: todos }).where(({ todo }) => {
if (userId === null) {
return undefined;
}
return eq(todo.userId, userId);
}),
});
// Good: 入力が揃わなければ query ごと無効化し、where にはどの分岐でも完全な式を返す
useLiveQuery({
query: (q) => {
if (userId === null) {
return undefined;
}
return q.from({ todo: todos }).where(({ todo }) => {
const active = and(eq(todo.userId, userId), eq(todo.archived, false));
if (status === null) {
return active;
}
return and(active, eq(todo.status, status));
});
},
});なぜ: .where() は式を要求し、undefined を返すと throw する。query が動くかどうかは先頭のガード句 1 か所、何で絞るかは where の中の式、と分けておけば、どの入力が欠けたら止まるかと、絞り込みの条件を別々に読める。
無効化された query の isLoading は false。無効化の条件も loading に含める
無効化された query は読み込み中ではないので isLoading: false を返す。query 側の isLoading だけを見ると、入力が決まる前の状態が「0 件」の空表示として描かれる。hook が返す isLoading には、無効化の条件を含める。
const { data, isLoading } = useLiveQuery({
query: (q) => {
if (userId === null) {
return undefined;
}
return q.from({ todo: todos }).where(({ todo }) => eq(todo.userId, userId));
},
});
// Bad: 無効化中は isLoading が false なので、userId が決まる前に空表示が出る
return { todos: data ?? [], isLoading };
// Good: 無効化の条件も loading に含める
return { todos: data ?? [], isLoading: userId === null || isLoading };例外は、入力が決まらない状態が正常に続く画面(選択が無いまま使うのが普通の画面)。無効化を loading に含めると永久に読み込み中のままになるので、loading にはせず、その状態の表示を出す。0 件や「見つかりません」の表示のほか、登録フォームのように、その画面の通常の内容を出してもよい。
なぜ: 無効化された query は何も読んでいないだけで読み込み中ではないので、isLoading は立たない。view にとっては「まだ何も読めていない」状態で、「読んだ結果が 0 件」の空表示とは別物。
useLiveQuery は config 形式で書き、deps 配列を渡さない
useLiveQuery({ query: (q) => ... }) の config 形式で書き、第 2 引数の deps 配列は使わない。closure の値(選択中の id など)は式に埋め込めば query の identity に入り、値が変わると再実行される。
// Bad: deps 配列を渡している
useLiveQuery(
(q) => q.from({ todo: todos }).where(({ todo }) => eq(todo.userId, userId)),
[userId],
);
// Good: config 形式。userId は式に埋め込まれるので deps は要らない
useLiveQuery({
query: (q) => q.from({ todo: todos }).where(({ todo }) => eq(todo.userId, userId)),
});なぜ: react-db は deps 配列を型定義で deprecated と明記している。config 形式なら、query の identity が式そのものから決まるので、deps の書き忘れで古い値の query が残ることがない。

