Skip to content
Advertisement

Retrieve value from IndexedDB using Dexie and Svelte

I don’t understand how I can get a value from IndexedDB using Dexie. Database is all good in ‘application’ tab in inspect tool. Total newbie, so please be understanding.

My db.js

import Dexie from "dexie";

export const db = new Dexie("myDatabase");
db.version(2).stores({
  history: "++id, daterange, days",
  storage: "id, name, value"
});

db.on("populate", function () {
  db.storage.add({
    id: 0,
    name: "total",
    value: 20
  });
  db.storage.add({
    id: 1,
    name: "left",
    value: 20
  });
});
db.open();

App.svelte

<script>
  import Counter from "./src/Counter.svelte";
  import New from "./src/New.svelte";
  import History from "./src/History.svelte";
  import { liveQuery } from "dexie";
  import { db } from "./src/db";

  let total = liveQuery(() =>
    db.storage
      .where("name")
      .equals("total")
      .value.then(function(a) {
        totals = a;
      })
  );

  let left = 25;
</script>

<style>
  main {
    width: 100%;
  }
</style>

<main>
    <Counter daysLeft={left} daysMax={total}/>
    <New />
    <History />
</main>

Whatever I try, object with daysMax={total} outputs undefined, [object Object] or something like [Dexie object Object]. I just want to get 20 from db, as seen in db.js:

db.on("populate", function () {
  db.storage.add({
    id: 0,
    name: "total",
    value: 20
  });

(This all works and is visible in indexedDb) I also tried daysMax={$total}

CodeSandbox

Advertisement

Answer

As you stated, your DB setup and initial write operations are done correctly. The issue is with your query:

  let total = liveQuery(() =>
    db.storage
      .where("name")
      .equals("total")
      .value.then(function(a) {
        totals = a;
      })
  );

First of all, the WhereClause part of your query (db.storage.where("name").equals("total")) returns a Collection, which, to quote the documentation:

Represents a collection of database objects. Note that it will not contain any objects by itself. Instead, it yields a preparation for how to execute a DB query. A query will be executed when calling methods that returns a Promise, such as toArray(), keys(), count() or each().

So you cannot simply dereference .value as you do in your code. You must instead use one of the methods provided by Collection. Since in your use case you only ever expect a single match to be found in the database, I would recommend Collection.first() here, which returns a Promise that eventually resolves to the data you’re querying.

So your query should instead look like this:

  let total = liveQuery(() =>
    db.storage
      .where("name")
      .equals("total")
      .first()
  );

Next is the way you use liveQuery. What liveQuery() does is it turns a Promise (which you will now receive from the previous step) into an Observable. As stated in the documentation:

The Svelte Store Contract is a subset of the Ecmascript Observable specification draft which makes the return value of liveQuery() a fully valid Svelte Store by itself.

Meaning here that total will in fact behave like a Svelte (readable) store, that can be subscribed to, or accessed with the shorthand $ syntax. This means that $total will hold – once the query completes and the promise resolves – the database object that matches your query.

And because it is an Observable (and thus a Svelte store), any updates made to the database record will be immediately reflected in the value of $total.

The last thing to be done is to access the value property of the database object. Because $total will initially be undefined while the query runs and the promise resolves, I would also add a conditional. For this I would recommend using a small reactive declaration to keep things tidy, picking a variable name that will allow you to use shorthand prop notation, combined with a conditional to only display the counter when the query has resolved:

<script>
    // ...your existing script code with updated query...
    $: daysMax = $total?.value // undefined until the query resolves, actual value once the query has resolved
</script>

<main>
    {#if daysMax !== undefined}
        <Counter daysLeft={left} {daysMax} />
    {/if}
    <New />
    <History />
</main>

Forked, working CodeSandbox

Advertisement