There is still a problem with the current implementation of useQuery
: type-safety.
The return type of useLiveQuery
is untyped. We need to decode the data to make sure it matches the expected type.
We do this using Schema
from effect
. For each query, we define a new Schema
responsible for decoding the data.
We add a generic Schema
parameter to useQuery
to validate the data:
export const useQuery = <A, I>(
query: (orm: ReturnType<typeof usePgliteDrizzle>) => Query,
schema: Schema.Schema<A, I>
) => {
const orm = usePgliteDrizzle();
const { params, sql } = query(orm);
// 👇 Mark return type as `I` (encoded data)
return useLiveQuery<I>(sql, params);
};
Since useLiveQuery
returns a list of values, we need to extract them and use Schema.decode
:
- If the data is
undefined
, it means the query has not been executed yet and we return aMissingData
error - If decoding fails, it means the data is invalid and we return a
InvalidData
error
We wrap the
schema
parameter insideSchema.Array
. We also usedecodeEither
to handle the error usingEither
.
export const useQuery = <A, I>(
query: (orm: ReturnType<typeof usePgliteDrizzle>) => Query,
schema: Schema.Schema<A, I>
) => {
const orm = usePgliteDrizzle();
const { params, sql } = query(orm);
const results = useLiveQuery<I>(sql, params);
return pipe(
results?.rows,
// 👇 `rows` data not yet available
Either.fromNullable(() => new MissingData()),
Either.flatMap(
flow(
Schema.decodeEither(Schema.Array(schema)),
// 👇 Invalid encoded data
Either.mapLeft((parseError) => new InvalidData({ parseError }))
)
)
);
};
With this all we need to make the reactive hooks type-safe is defining and adding a Schema
for each query:
export class FoodSelect extends Schema.Class<FoodSelect>("FoodSelect")({
id: PrimaryKeyIndex,
name: Schema.NonEmptyString,
brand: Schema.NullOr(Schema.NonEmptyString),
calories: FloatQuantityInsert,
carbohydrates: FloatQuantityInsert,
proteins: FloatQuantityInsert,
fats: FloatQuantityInsert,
fatsSaturated: FloatQuantityOrUndefined,
salt: FloatQuantityOrUndefined,
fibers: FloatQuantityOrUndefined,
sugars: FloatQuantityOrUndefined,
}) {}
export const useFoods = () => {
return useQuery(
(orm) => orm.select().from(foodTable).toSQL(),
FoodSelect, // 👈 Add schema
);
};
With this all the hooks using useQuery
are type-safe and fully validated.
We can handle missing or invalid data by extracting the value from
Either
returned by the hook.