DexieJS is a wrapper over IndexedDB that supports live/reactive queries.
Live queries observe the result and re-render your component with the data in real time.
This snippet implements a complete setup of dexie with effect to execute type-safe queries in React.
Dexie service
dexie.ts is an effect service used to initialize the database and implement the queries.
formAction is a generic function used for all queries:
- Extract data from
FormData - Validate data using
Schema - Execute database query
// 👇 Action specific to decode `FormData`
const formAction =
<const R extends string, I, T>(
source: Schema.Schema<I, Record<R, string>>,
exec: (values: Readonly<I>) => Promise<T>
) =>
(formData: FormData) =>
// 1️⃣ Decode `FormData` to `Record<string, string>`
Schema.decodeUnknown(source)(formDataToRecord(formData)).pipe(
Effect.mapError((error) => new WriteApiError({ cause: error })),
Effect.flatMap((values) =>
Effect.tryPromise({
// 2️⃣ Execute the query
try: () => exec(values),
catch: (error) => new WriteApiError({ cause: error }),
})
)
);While
formActionis designed for forms andFormData(e.g.<form action={action}>),changeActionworks for anyPayload(e.g.<input onChange={(e) => action(event.target.value)}>).
Live queries with effect
useDexieQuery wraps useLiveQuery to execute live queries and provide type-safe error and loading states:
- Execute query using
dexieinstance - If result is
undefined, thenloading = true - If the query fails or the data is invalid, then set
error - Otherwise return the valid type-safe data
useLiveActivitiesshows an example of how to useuseDexieQuery. The return value containsdata,error, andloading.
datais defined whenloading === falseanderror !== undefined.
import { ActivityTable } from "./schema";
import { useDexieQuery } from "./use-dexie-query";
export const useLiveActivities = () => {
// 👇 Get data with validation with `Schema`
const { data, error, loading } = useDexieQuery(
(_) => _.activity.toArray(),
ActivityTable
);
return { data, error, loading }; // 👈 Return data with validation
};Insert query
useActionEffect is a wrapper on useActionState to extract any Payload and execute actions in React.
Check out the
useActionEffectsnippet to learn more about how it works.
useInsertActivity shows an example of using useActionEffect and Dexie to execute a query.
By enabling accessors inside Effect.Service in Dexie the API is reduced to a single function call, with all types inferred:
import { Dexie } from "./dexie";
import { useActionEffect } from "./use-action-effect";
export const useInsertActivity = () => {
// 👇 Final API is as easy as it gets (all types inferred!)
const [{ error, data }, action, pending] = useActionEffect(
Dexie.insertActivity // ✨ Magic with accessors
);
};Check out Make FormData and input names type-safe in React for more details on how it's possible to make
FormDatatype-safe inside anyform.
These are the steps to use this setup in your own application:
- Define table schemas inside
schema.ts(useSchemafromeffectfor validation) - Add schemas to
dexieinstance insidedexie.ts(when definingdb) - Make sure to define indexes with
.storesinsidedexie.ts - Implement and export queries using
formAction/changeActionin the return value of theDexieservice (dexie.ts) - Define live queries using
useDexieQuery - Define insert/update/delete requests using
useActionEffect
You can view an example of a complete application that uses this dexie setup in the repository linked below: