How IndexedDB Actually Works

IndexedDB has:

  • Database
  • Object Stores (like tables)
  • Transactions
  • Indexes
  • Requests (async events)

Flow:

  1. Open database
  2. Create object stores (only in upgrade phase)
  3. Start transaction
  4. Perform read/write
  5. Handle success/error

Important:

Opening DB is async and version-controlled.

Step 1 — Creating a Simple IndexedDB Utility (Clean Way)

We’ll build a reusable wrapper first.

db.ts (Minimal Production-Ready Wrapper)

const DB_NAME = "MyAppDB";
const DB_VERSION = 1;
const STORE_NAME = "items";

export function openDB(): Promise<IDBDatabase> {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(DB_NAME, DB_VERSION);

    request.onupgradeneeded = (event) => {
      const db = request.result;

      if (!db.objectStoreNames.contains(STORE_NAME)) {
        db.createObjectStore(STORE_NAME, { keyPath: "id" });
      }
    };

    request.onsuccess = () => {
      resolve(request.result);
    };

    request.onerror = () => {
      reject(request.error);
    };
  });
}

Step 2 — CRUD Operations

Add Item

export async function addItem(item: any) {
  const db = await openDB();

  return new Promise((resolve, reject) => {
    const transaction = db.transaction(STORE_NAME, "readwrite");
    const store = transaction.objectStore(STORE_NAME);

    const request = store.put(item);

    request.onsuccess = () => resolve(true);
    request.onerror = () => reject(request.error);
  });
}

Get All Items

export async function getAllItems() {
  const db = await openDB();

  return new Promise((resolve, reject) => {
    const transaction = db.transaction(STORE_NAME, "readonly");
    const store = transaction.objectStore(STORE_NAME);

    const request = store.getAll();

    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

Step 3 — Using IndexedDB in React

Now we integrate into a React component properly.

Example: Offline Task List

import { useEffect, useState } from "react";
import { addItem, getAllItems } from "./db";

interface Task {
  id: number;
  title: string;
}

export default function App() {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [input, setInput] = useState("");

  useEffect(() => {
    loadTasks();
  }, []);

  const loadTasks = async () => {
    const stored = (await getAllItems()) as Task[];
    setTasks(stored);
  };

  const handleAdd = async () => {
    const newTask = {
      id: Date.now(),
      title: input,
    };

    await addItem(newTask);
    setInput("");
    loadTasks();
  };

  return (
    <div>
      <h2>IndexedDB Task List</h2>

      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
      />

      <button onClick={handleAdd}>Add</button>

      <ul>
        {tasks.map((task) => (
          <li key={task.id}>{task.title}</li>
        ))}
      </ul>
    </div>
  );
}

What Is Happening Internally

When component mounts:

  1. useEffect runs
  2. getAllItems() opens DB
  3. Starts readonly transaction
  4. Gets all records
  5. Updates React state

When adding:

  1. addItem() opens DB
  2. Starts readwrite transaction
  3. Writes item
  4. React reloads data

Avoid Reopening DB Every Time

Right now openDB() runs on every call.

Better pattern:

  • Open DB once
  • Cache reference
  • Reuse it

Cleaner Pattern: Using a Singleton DB Instance

let dbInstance: IDBDatabase | null = null;

export async function getDB() {
  if (dbInstance) return dbInstance;

  dbInstance = await openDB();
  return dbInstance;
}

Now use getDB() inside CRUD functions.

This avoids repeated open calls.

Even Better — Use idb Library

Raw IndexedDB is verbose and event-based.

In real production, most teams use:

idb

It wraps IndexedDB in Promise syntax.

Example:

import { openDB } from "idb";

const db = await openDB("MyAppDB", 1, {
  upgrade(db) {
    db.createObjectStore("items", { keyPath: "id" });
  },
});

await db.put("items", { id: 1, title: "Hello" });
const all = await db.getAll("items");

When Do We Use IndexedDB in React Apps?

Use it when:

  • Building offline-first apps
  • Caching API responses
  • Storing large datasets
  • Storing files/blobs
  • PWA implementations

Not for:

Small temporary state

Simple UI preferences (use localStorage)