Skip to content

Dexie v3.1.0-alpha.1

Pre-release
Pre-release
Compare
Choose a tag to compare
@dfahlander dfahlander released this 19 Nov 18:17
· 954 commits to master since this release

Dexie.js with Built-in observability

This version gives Dexie.js built-in observability which has been one of the Visions for dexie, earlier named db.observe().

It's called liveQuery() instead because it is not tied to a single db instance but it is capable of turning an expression or a async function with several statements and a final return value, into a observable (compatible with the es-observable spec).

import Dexie, { liveQuery } from "dexie";

// Turn any promise-returning function into an observable:
const observable = liveQuery(
  () => getFriendsWithContactInfos({minAge: 18, maxAge: 64})
);

// The function can do several requests in a flow and/or in paralell
// and finally return a result.
// The result is what is being observed:
async function getFriendsWithContactInfos ({minAge, maxAge}) {
  // Get all friends within range:
  const friends = await db.friends
    .where('age').between(minAge, maxAge, true, true)
    .toArray();

  // Map an array of contactInfos related to each friend:
  const contactInfos = await Promise.all(friends =>
    db.contactInfos.where({friendId: friend.id})
        .toArray()
  );

  // Put the resolved contactInfos as an array prop on each friend to return
  friends.forEach((friend, idx) => friend.contactInfos = contactInfos[idx]);
  return friends; // This result is what will be observed by the observable.
}

Ok, so now we have an observable of this. Let's subscribe to it:

const subscription = observable.subscribe({
  next: result => console.log("Got result:", result),
  error: console.error
});

Lets update something that would affect the result of the query:

db.contactInfos.add({friendId: 2, type: "phoneNo", value: "1234567"});

When the above statement has been successfully committed to the DB, the function will re-execute if and only if friend with id 2 is within the queried age range (!), and our subscriber will be called with the new result of the callback.

The main reason for this new feature is better integration with frontend libraries like angular and react.
Especially for react, I've also release a new library dexie-react-hooks with the hook useLiveQuery() that will make data reactive data-fetching from IndexedDB very easy. Angular uses observables natively - so probably our ES-compliant observable can be used as it is.

A new library dexie-react-hooks

This library integrates React with the new liveQuery functionality described above

export function useLiveQuery<T, TDefault=undefined> (
  querier: () => Promise<T> | T,
  deps?: any[], // ...like deps for useMemo(). Defaults to empty array.
  defaultResult?: TDefault // Default value returned while data is loading
) : T | TDefault;

Example

const db = new Dexie('news-db');
db.version(1).stores({
  articles: '++id, title',
  articleLikes: '[articleId+userId]'
});

function MyComponent({articleId, currentUser}) {
  const article = useLiveQuery(() => db.articles.get(articleId), [articleId]);
  const numLikes = useLiveQuery(() => db.articleLikes.where({articleId: articleId}).count(), [articleId]);
  return <>
    <Article article={article} numLikes={numLikes} />
    <button onClick={() => db.articleLikes.put({userId: currentUser.id, articleId})}>
      Like! {/* clicking button will increase like count if current user hadnt liked it before */}
    </button>
  </>;
}

See more examples in dexie-react-hooks