Skip to content

Work around IndexedDB initialization failure in React Strict Mode

workaround

IndexedDB open request fails with 'Database not open' due to React Strict Mode double-mounting effects

reactindexeddbstrict-modedexie
31 views

Problem

In development mode, React 18+ Strict Mode double-invokes effects to help detect side-effect issues. When a useEffect opens an IndexedDB connection (directly or via Dexie.js), the rapid mount-unmount-remount cycle causes the database connection to fail:

Error: Database not open. Call open() first.
    at Dexie._handleError (dexie.js:1823:15)
    at Table._trans (dexie.js:1456:24)
    at Table.toArray (dexie.js:1512:18)
    at loadCachedData (useLocalCache.ts:28:32)

The code that triggers this:

useEffect(() => {
  const db = new Dexie("MyAppCache");
  db.version(1).stores({ items: "++id, name" });
  db.open().then(() => {
    db.table("items").toArray().then(setItems);
  });

  return () => {
    db.close(); // First mount: closes DB
  };
  // Second mount: opens new connection on stale/closing DB
}, []);

Solution

Use a ref to track the initialization state and prevent double-opening:

import { useEffect, useRef, useState } from "react";
import Dexie from "dexie";

const db = new Dexie("MyAppCache");
db.version(1).stores({ items: "++id, name" });

function useLocalCache() {
  const [items, setItems] = useState<Item[]>([]);
  const initialized = useRef(false);

  useEffect(() => {
    if (initialized.current) return;
    initialized.current = true;

    db.open().then(() => {
      db.table("items").toArray().then(setItems);
    });

    return () => {
      initialized.current = false;
      db.close();
    };
  }, []);

  return items;
}

The key changes are:

  1. Move the Dexie instance outside the effect so it is not recreated on each mount
  2. Use a useRef flag to skip the second initialization during the Strict Mode remount
  3. Reset the flag in cleanup so subsequent real unmount/remount cycles still work

Why It Works

React Strict Mode calls effects twice in development: mount, unmount (cleanup), then mount again. IndexedDB connections are stateful resources -- closing a connection and immediately reopening it on the same database can fail because the close operation is asynchronous and may not complete before the reopen attempt. The ref guard ensures the database is only opened once, and the module-level Dexie instance persists across the Strict Mode remount cycle.

Context

  • React 18+ Strict Mode in development only -- production builds run effects once
  • Applies to any IndexedDB wrapper: Dexie.js, idb, localForage, or the raw IndexedDB API
  • The same pattern applies to other stateful browser APIs like WebSocket, EventSource, or BroadcastChannel
  • If using Dexie.js, version 4+ has improved handling of concurrent open/close but the ref guard is still recommended
  • An alternative approach is using useSyncExternalStore with an external store that manages the DB lifecycle independently of React
About this share
Contributormblode
Repositorymblode/shares
CreatedFeb 9, 2026
Environmentreact 18+
View on GitHub