Skip to content

useDocument merge() calls without save() - clients expect automatic persistence #1188

@jchris

Description

@jchris

Problem Description

Clients are generating code that calls merge() without save(), expecting the changes to be automatically persisted to the database. However, merge() is designed as a setState replacement for local updates (e.g., every keystroke) and does not automatically save to the database.

Example Code

import React from "react"
import { useFireproof } from "use-fireproof"

export default function PageToggle() {
  const { database, useDocument } = useFireproof("page-toggle-db")
  const { doc, merge } = useDocument({ isWhite: false, type: "page-state" })

  const togglePage = () => {
    merge({ isWhite: !doc.isWhite })  // ❌ Changes local state but doesn't persist
  }

  return (
    <div>
      <button onClick={togglePage}>Toggle Page</button>
      <div style={{ backgroundColor: doc.isWhite ? 'white' : 'black' }}>
        Current state: {doc.isWhite ? 'White' : 'Black'}
      </div>
    </div>
  )
}

Current Behavior vs Expected Behavior

Current Behavior:

  • merge() updates local component state only
  • Changes are lost on page refresh/component unmount
  • Requires explicit save() call for persistence

Expected Behavior (by clients):

  • merge() changes should be automatically persisted
  • State should survive page refreshes
  • No manual save() required for simple state updates

Impact

This creates a poor developer experience where:

  1. Clients write code that appears to work (UI updates)
  2. Data is lost unexpectedly on refresh
  3. Confusion about when persistence actually happens
  4. Need to remember to call save() for every merge()

Potential Solutions

Option 1: Auto-save with Debouncing

Add automatic persistence to merge() with configurable debouncing:

const { doc, merge } = useDocument({ 
  isWhite: false 
}, { 
  autoSave: true,           // Enable auto-save on merge
  autoSaveDelay: 300        // Debounce delay in ms
});

Option 2: Auto-save on Unload

Automatically save pending changes when component unmounts or page unloads:

// Save any pending changes on cleanup
useEffect(() => {
  return () => {
    if (hasUnsavedChanges) {
      save();
    }
  };
}, []);

Recommended Approach

Option 1 + 2 (Auto-save with Debouncing) seems most practical:

  • Maintains backward compatibility
  • Provides expected behavior for simple use cases
  • Allows opt-out for performance-sensitive scenarios
  • Handles both frequent updates and state changes appropriately

Implementation Considerations

  1. Performance: Auto-save should be debounced to avoid excessive database writes
  2. Error Handling: Failed auto-saves should not break the UI
  3. Opt-out: Developers should be able to disable auto-save for performance-critical components
  4. Migration: Existing code should continue to work unchanged
  5. Documentation: Clear guidance on when to use auto-save vs manual save

Related Files

  • use-fireproof/react/use-document.ts (line 65-68: merge implementation)
  • notes/use-document.md (documentation of current behavior)

This issue affects developer experience and could prevent adoption due to unexpected data loss.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions