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
formAction
is designed for forms andFormData
(e.g.<form action={action}>
),changeAction
works 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
dexie
instance - 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
useLiveActivities
shows an example of how to useuseDexieQuery
. The return value containsdata
,error
, andloading
.
data
is defined whenloading === false
anderror !== 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
useActionEffect
snippet 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
FormData
type-safe inside anyform
.
These are the steps to use this setup in your own application:
- Define table schemas inside
schema.ts
(useSchema
fromeffect
for validation) - Add schemas to
dexie
instance insidedexie.ts
(when definingdb
) - Make sure to define indexes with
.stores
insidedexie.ts
- Implement and export queries using
formAction
/changeAction
in the return value of theDexie
service (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: