useLiveQuery()
Remarks
Observe IndexedDB data in your React component. Make the component re-render when the observed data changes.
See Also
Dependencies
npm i react dexie dexie-react-hooks
or
yarn add react dexie dexie-react-hooks
Syntax
export function useLiveQuery<T, TDefault=undefined> (
querier: () => Promise<T> |Â T,
deps?: any[], // ...like deps argument in useEffect() but defaults to empty array.
defaultResult?: TDefault // Default value returned while data is loading
) : T | TDefault;
Parameter | Description |
---|---|
querier | Function that returns a final result (Promise) |
deps | Variables that querier is dependent on (similar to the deps argument in useEffect() ). |
defaultResult | Result returned on initial render - before the promise have resolved. |
Rules for the querier function
- Don’t call asynchronous API:s from it except Dexie’s APIs.
- If you really need to call other async API’s (such as fetch() or webCrypto), wrap the returned promise through
Promise.resolve()
. There’s an example later in this page on how to do that.
Simple Example
import React from "react";
import Dexie from "dexie";
import { useLiveQuery } from "dexie-react-hooks";
import { db } from "./db";
//
// React component
//
export function OldFriendsList() {
const friends = useLiveQuery(
() => db.friends
.where('age')
.above(75)
.toArray()
);
if (!friends) return null; // Still loading.
return <ul>
{ friends.map(friend =>
<li key={friend.id}>
{friend.name}, {friend.age}
</li>)
}
</ul>;
}
See also this stackoverflow question (and answer) on how to determine whether a response is still loading or has returned an undefined result
Persistent State Manager
The useLiveQuery()
hook does not only load data - it observes the query for changes. This means that you can use Dexie as a persistent and RAM-sparse state manager in your React application since you don’t keep entire database in RAM. If you add, update or delete a friend using Dexie methods for that (such as Table.add, Table.update(), Table.delete(), …etc), any component that observes the affected data will automatically rerender.
Fine grained observation
The observation is as fine-grained as it can possibly be - queries that would be affected by a modification will rerender - others not (with some exceptions - false positives happen but never false negatives). This is also true if your querier callback performs a series of awaited queries or multiple in parallel using Promise.all(). It can even contain if-statements or other conditional paths within it, determining additional queries to make before returning a final result - still, observation will function and never miss an update. No matter how simple or complex the query is - it will be monitored in detail so that if a single part of the query is affected by a change, the querier will be executed and the component will rerender.
Limitations
- Changes must have been made using Dexie.js: This hook will only observe changes made using Dexie.js. Changes from devtools or another IndexedDB wrapper will not be observed. However, as long as changes were made using Dexie.js version 3.1 or later - be it from a service worker, web worker, other tab or window, the hook will react instantly to any changes affecting the query it observes. This functionality is built-in into Dexie.js itself and a react app will also be able to observe changes made from another app that does not have dexie-react-hooks installed. Since there is no native API for this kind of observation, Dexie made it a first-class citizen for all that use Dexie.js as persistence layer.
- Same origin only: IndexedDB is a per-origin database, meaning that offline data stored for one website cannot read data for another website unless they share the same origin. useLiveQuery() will naturally have the same boundary between origins. Syncing data between origins, clients and different browsers or devices require another solution and can be accomplished using our addon dexie-cloud-addon and our commercial service Dexie Cloud.
- Avoid calling other API’s from your querier callback. See Calling non-Dexie APIs from querier below.
Enhanced Example
This example shows that…
- you can observe the result of an arbitrary function that queries Dexie
- you can use a state from a useState() result within your querier function (just need to mention it in the deps array)
- the component will re-render if the data you are querying change
- the component will re-render if in-parameter to the query change.
- the query will change when state change.
import React, { useState } from "react";
import { useLiveQuery } from "dexie-react-hooks";
import { db } from "../db";
export function FriendList() {
const [maxAge, setMaxAge] = useState(21);
// Query friends within a certain range decided by state:
const friends = useLiveQuery(
() => db.friends.where("age").belowOrEqual(maxAge).sortBy("id"),
[maxAge] // because maxAge affects query!
);
// Example of another query in the same component.
const friendCount = useLiveQuery(() => db.friends.count());
// If default values are returned, queries are still loading:
if (!friends || friendCount === undefined) return null;
return (
<div>
<p>
Your have <b>{friendCount}</b> friends in total.
</p>
<label>
Please enter max age to query:
<input
type="number"
value={maxAge}
onChange={(ev) => setMaxAge(parseInt(ev.target.value, 10))}
/>
</label>
<ul>
{friends.map((friend) => (
<li key={friend.id}>
{friend.name}, {friend.age}
<button
onClick={() =>
db.friends.where({ id: friend.id }).modify((f) => ++f.age)
}
>
Birthday!
</button>
</li>
))}
</ul>
</div>
);
}
Decoupling
The expression passed to useLiveQuery() must be a function that returns a promise. If you need to decouple your component from the db, you can provide the querying functions as callbacks instead:
export function FriendList({getFriendCount, getFriendsByAge, onBirthdayClick}) {
...
const friendCount = useLiveQuery(getFriendCount);
const friends = useLiveQuery(
() => getFriendsByAge(maxAge), [maxAge]
);
...
// And the button's onClick event:
<button ... onClick={()=>onBirthdayClick(friend)}>...</button>
}
// ...and implement the callback elsewhere...
function App () {
const getFriendCount = () => db.friends.count();
const getFriendsByAge = maxAge =>
db.friends
.where('age')
.belowOrEqual(maxAge)
.sortBy('id');
const onBirthdayClick = friend =>
db.friends.where({ id: friend.id }).modify(f => ++f.age);
return <FriendList
fetchFriendCount={getFriendCount}
fetchFriendsByAge={getFriendsByAge}
onBirthdayClick={onBirthdayClick} />;
}
Calling non-Dexie API:s from querier
If your querier callback needs to call asynchronous non-Dexie APIs to resolve its result, the promises returned by those non-Dexie API:s needs to be wrapped using Promise.resolve()
. This is needed in order to keep the observation context alive between async calls. Even though APIs like fetch()
, webCrypto
etc already returns promises, this is still needed in order for useLiveQuery() to function properly. It might feel unnecessary to wrap it with Promise.resolve()
when it’s already a promise being returned, but this is a rule that needs to be followed when using useLiveQuery()
with non-Dexie APIs. As of Dexie 3.2.0, you might not notice any warning or error if not following this rule but in a future version of Dexie, it might start throwing some explanatory error if this rule has been forgotten.
function MyComponent(id) {
const friendWithMetaData = useLiveQuery(async () => {
// Normal Dexie call:
const friend = await db.friends.get(id);
// Calling non-Dexie API - always wrap with Promise.resolve():
try {
const friendMetaData = await Promise.resolve (
// Ok to call fetch if we wrap it with Promise.resolve()
fetch(friend.metaDataUrl).then(res => res.json())
);
friend.metaData = friendMetaData;
} catch (error) {
friend.metaData = null;
}
return friend;
}, [id]);
return <>
<p>Name: {friendWithMetaData.name}</p>
<p>FooBar: {friendWithMetaData.metaData?.fooBar}</p>
</>;
}
Playgrounds
Another sample using useLiveQuery() on Stackblitz
The sample from this page in CodeSandbox
See also
Table of Contents
- API Reference
- Access Control in Dexie Cloud
- Add demo users
- Add public data
- Authentication in Dexie Cloud
- Best Practices
- Building Addons
- Collection
- Collection.and()
- Collection.clone()
- Collection.count()
- Collection.delete()
- Collection.desc()
- Collection.distinct()
- Collection.each()
- Collection.eachKey()
- Collection.eachPrimaryKey()
- Collection.eachUniqueKey()
- Collection.filter()
- Collection.first()
- Collection.keys()
- Collection.last()
- Collection.limit()
- Collection.modify()
- Collection.offset()
- Collection.or()
- Collection.primaryKeys()
- Collection.raw()
- Collection.reverse()
- Collection.sortBy()
- Collection.toArray()
- Collection.uniqueKeys()
- Collection.until()
- Compound Index
- Consistency in Dexie Cloud
- Consistent add() operator
- Consistent remove() operator
- Consistent replacePrefix() operator
- Consuming Dexie as a module
- Custom Emails in Dexie Cloud
- DBCore
- DBCoreAddRequest
- DBCoreCountRequest
- DBCoreCursor
- DBCoreDeleteRangeRequest
- DBCoreDeleteRequest
- DBCoreGetManyRequest
- DBCoreGetRequest
- DBCoreIndex
- DBCoreKeyRange
- DBCoreMutateRequest
- DBCoreMutateResponse
- DBCoreOpenCursorRequest
- DBCorePutRequest
- DBCoreQuery
- DBCoreQueryRequest
- DBCoreQueryResponse
- DBCoreRangeType
- DBCoreSchema
- DBCoreTable
- DBCoreTableSchema
- DBCoreTransaction
- DBCoreTransactionMode
- DBPermissionSet
- Deprecations
- Derived Work
- Design
- Dexie Cloud API
- Dexie Cloud API Limits
- Dexie Cloud Best Practices
- Dexie Cloud CLI
- Dexie Cloud Docs
- Dexie Cloud REST API
- Dexie Cloud Web Hooks
- Dexie Constructor
- Dexie.AbortError
- Dexie.BulkError
- Dexie.ConstraintError
- Dexie.DataCloneError
- Dexie.DataError
- Dexie.DatabaseClosedError
- Dexie.IncompatiblePromiseError
- Dexie.InternalError
- Dexie.InvalidAccessError
- Dexie.InvalidArgumentError
- Dexie.InvalidStateError
- Dexie.InvalidTableError
- Dexie.MissingAPIError
- Dexie.ModifyError
- Dexie.NoSuchDatabaseErrorError
- Dexie.NotFoundError
- Dexie.Observable
- Dexie.Observable.DatabaseChange
- Dexie.OpenFailedError
- Dexie.PrematureCommitError
- Dexie.QuotaExceededError
- Dexie.ReadOnlyError
- Dexie.SchemaError
- Dexie.SubTransactionError
- Dexie.Syncable
- Dexie.Syncable.IDatabaseChange
- Dexie.Syncable.IPersistentContext
- Dexie.Syncable.ISyncProtocol
- Dexie.Syncable.StatusTexts
- Dexie.Syncable.Statuses
- Dexie.Syncable.registerSyncProtocol()
- Dexie.TimeoutError
- Dexie.TransactionInactiveError
- Dexie.UnknownError
- Dexie.UnsupportedError
- Dexie.UpgradeError()
- Dexie.VersionChangeError
- Dexie.VersionError
- Dexie.[table]
- Dexie.addons
- Dexie.async()
- Dexie.backendDB()
- Dexie.close()
- Dexie.currentTransaction
- Dexie.debug
- Dexie.deepClone()
- Dexie.defineClass()
- Dexie.delByKeyPath()
- Dexie.delete()
- Dexie.derive()
- Dexie.events()
- Dexie.exists()
- Dexie.extend()
- Dexie.fakeAutoComplete()
- Dexie.getByKeyPath()
- Dexie.getDatabaseNames()
- Dexie.hasFailed()
- Dexie.ignoreTransaction()
- Dexie.isOpen()
- Dexie.js
- Dexie.name
- Dexie.on()
- Dexie.on.blocked
- Dexie.on.close
- Dexie.on.error
- Dexie.on.populate
- Dexie.on.populate-(old-version)
- Dexie.on.ready
- Dexie.on.storagemutated
- Dexie.on.versionchange
- Dexie.open()
- Dexie.override()
- Dexie.semVer
- Dexie.setByKeyPath()
- Dexie.shallowClone()
- Dexie.spawn()
- Dexie.table()
- Dexie.tables
- Dexie.transaction()
- Dexie.transaction()-(old-version)
- Dexie.use()
- Dexie.verno
- Dexie.version
- Dexie.version()
- Dexie.vip()
- Dexie.waitFor()
- DexieCloudOptions
- DexieError
- Docs Home
- Download
- EntityTable
- Export and Import Database
- Get started with Dexie in Angular
- Get started with Dexie in React
- Get started with Dexie in Svelte
- Get started with Dexie in Vue
- Hello World
- How To Use the StorageManager API
- Inbound
- IndexSpec
- Indexable Type
- IndexedDB on Safari
- Invite
- Member
- Migrating existing DB to Dexie
- MultiEntry Index
- PersistedSyncState
- Privacy Policy
- Promise
- Promise.PSD
- Promise.catch()
- Promise.finally()
- Promise.on.error
- Promise.onuncatched
- Questions and Answers
- Realm
- Releasing Dexie
- Road Map
- Road Map: Dexie 5.0
- Road Map: Dexie Cloud
- Role
- Run Dexie Cloud on Own Servers
- Sharding and Scalability
- Simplify with yield
- Support Ukraine
- SyncState
- Table
- Table Schema
- Table.add()
- Table.bulkAdd()
- Table.bulkDelete()
- Table.bulkGet()
- Table.bulkPut()
- Table.bulkUpdate()
- Table.clear()
- Table.count()
- Table.defineClass()
- Table.delete()
- Table.each()
- Table.filter()
- Table.get()
- Table.hook('creating')
- Table.hook('deleting')
- Table.hook('reading')
- Table.hook('updating')
- Table.limit()
- Table.mapToClass()
- Table.name
- Table.offset()
- Table.orderBy()
- Table.put()
- Table.reverse()
- Table.schema
- Table.toArray()
- Table.toCollection()
- Table.update()
- Table.where()
- The main limitations of IndexedDB
- Transaction
- Transaction.abort()
- Transaction.on.abort
- Transaction.on.complete
- Transaction.on.error
- Transaction.table()
- Tutorial
- Typescript
- Typescript (old)
- Understanding the basics
- UserLogin
- Version
- Version.stores()
- Version.upgrade()
- WhereClause
- WhereClause.above()
- WhereClause.aboveOrEqual()
- WhereClause.anyOf()
- WhereClause.anyOfIgnoreCase()
- WhereClause.below()
- WhereClause.belowOrEqual()
- WhereClause.between()
- WhereClause.equals()
- WhereClause.equalsIgnoreCase()
- WhereClause.inAnyRange()
- WhereClause.noneOf()
- WhereClause.notEqual()
- WhereClause.startsWith()
- WhereClause.startsWithAnyOf()
- WhereClause.startsWithAnyOfIgnoreCase()
- WhereClause.startsWithIgnoreCase()
- db.cloud.configure()
- db.cloud.currentUser
- db.cloud.currentUserId
- db.cloud.events.syncComplete
- db.cloud.invites
- db.cloud.login()
- db.cloud.logout()
- db.cloud.options
- db.cloud.permissions()
- db.cloud.persistedSyncState
- db.cloud.roles
- db.cloud.schema
- db.cloud.sync()
- db.cloud.syncState
- db.cloud.userInteraction
- db.cloud.usingServiceWorker
- db.cloud.version
- db.cloud.webSocketStatus
- db.members
- db.realms
- db.roles
- db.syncable.connect()
- db.syncable.delete()
- db.syncable.disconnect()
- db.syncable.getOptions()
- db.syncable.getStatus()
- db.syncable.list()
- db.syncable.on('statusChanged')
- db.syncable.setFilter()
- dexie-cloud-addon
- dexie-react-hooks
- liveQuery()
- unhandledrejection-event
- useLiveQuery()
- useObservable()
- usePermissions()