rocicorp / replicache

Realtime Sync for Any Backend Stack
https://doc.replicache.dev
1.05k stars 38 forks source link

IDB tx closes when two toArray() calls in same component #460

Closed aboodman closed 3 years ago

aboodman commented 3 years ago

This is bizarre: https://codesandbox.io/s/stoic-mendeleev-06ie8?file=/src/App.js

This creates:

InvalidStateError: Failed to execute 'objectStore' on 'IDBTransaction': The transaction has finished

If you remove one of the toArray() calls, it works correctly. If you put the two calls in separate useSubscribe calls, it works correctly.

See discussion here: https://discord.com/channels/830183651022471199/881071825403150366/881082736054517800

aboodman commented 3 years ago

This appears to be fixed on head.

npx create-next-app
cd my-app
npm add replicache replicache-react
npm install
npm run build

Then replace index.js with:

import { useEffect, useState } from "react";
import { Replicache } from "replicache";
import { useSubscribe } from "replicache-react";

function useTasks(rep, rootId) {
  return useSubscribe(
      rep,
      async (tx) => {
          const tasks = (await tx
              .scan({ indexName: 'tasksByRootId', prefix: rootId })
              .values().toArray())
          return tasks
      },
      [],
      []
  )
}

function useEvents(rep, rootId) {
  return useSubscribe(
      rep,
      async (tx) => {
          const events = (await tx
              .scan({ indexName: 'eventsByRootId', prefix: rootId })
              .values().toArray())
          return events
      },
      [],
      []
  )
}

function Body({ rep }) {
  console.log('rendered')
  const tasks = useTasks(rep, "2");
  const events = useEvents(rep, "2");
  return (
    <div>
      date: {new Date().toString()}<br/>
      tasks:
      <pre>{JSON.stringify(tasks)}</pre>
      events:
      <pre>{JSON.stringify(events)}</pre>
    </div>
  );
}

export default function App() {
  const [rep, setRep] = useState();
  const [inited, setInited] = useState(false);
  const [_, setState] = useState({});
  useEffect(() => {
    const rep = new Replicache({
      wasmModule: "https://unpkg.com/replicache@6.4.1/out/replicache.dev.wasm",
      mutators: {
        init: async (tx) => {
          await tx.put(`task/1`, { id: 1, rootID: "1" });
          await tx.put(`task/2`, { id: 2, rootID: "1" });
          await tx.put(`task/3`, { id: 3, rootID: "2" });
          await tx.put(`event/1`, { id: 1, rootID: "2" });
          await tx.put(`event/2`, { id: 2, rootID: "3" });
        },
      },
      pushDelay: 9999999,
      pullInterval: null
    });

    setRep(rep);
    async function init() {
      await rep.createIndex({
        name: "tasksByRootId",
        jsonPointer: "/rootID",
        keyPrefix: "task/"
      })
      await rep.createIndex({
        name: "eventsByRootId",
        jsonPointer: "/rootID",
        keyPrefix: "event/"
      })
      rep.mutate.init();
      setInited(true);
    }
    init();
    return () => {
      rep.close();
    };
  }, []);

  if (!inited) {
    return null;
  }

  return <div className="App" onClick={() => setState({})}>{rep ? <Body rep={rep} /> : null}</div>;
}

If you replace Replicache dep with trunk version problem seems to go away.

KeKs0r commented 3 years ago

It seems to be fixed in head (checked from the files in the discord thread). Could we make a release?

aboodman commented 3 years ago

Fixed by 233c460218dae991d41d10a0afa979f902112f7a - doing release now