polemius / recoil-persist

Package for recoil state manager to persist and rehydrate store
https://polemius.dev/recoil-persist/
MIT License
348 stars 40 forks source link

Compatibility with localForage #55

Open insaindesign opened 2 years ago

insaindesign commented 2 years ago

localForage is incompatible with recoil-storage, return types are different.

localForage.getItem() returns Promise<null|string>. which makes getState return null, as there is no check for null from a promise.

localForage.setItem() returns Promise<string>. but recoil-storage doesn't care about that anyway, so doesn't have to enforce those types.

Making getState return a Promise always simplifies the code a lot, though that commit doesn't need to be part of this, but it better enforces the types.

dende-h commented 1 year ago

Hello. Compatibility with localForage is what I am looking for. localStorage had capacity concerns. With this code I was able to save and update to IndexedDB. However, after the DB data is set on the first load, the update is set to the initial value in the onSet function. Therefore, after the initial load, if the reload is performed again without updating the data, the DB data will be lost. My atom code is as follows.

/* eslint-disable @typescript-eslint/no-empty-function /
/ eslint-disable @typescript-eslint/ban-ts-comment */
import { atom } from "recoil";
import { recoilPersist } from "../../components/util/customRecoilPersist";
import { draftObject } from "../selector/editorState";
import localforage from "localforage";

export type draftObjectArray = draftObject[];

localforage.config({
driver: localforage.INDEXEDDB,
name: "drafts",
version: 2,
storeName: "draftObject"
});

const { persistAtom } = recoilPersist({
key: "recoil-indexeddb",

storage: typeof window === "undefined" ? undefined : localforage
});

export const drafts = atom({
key: "drafts",
default: [],
effects_UNSTABLE: [persistAtom]
});
dende-h commented 1 year ago

Skipping onSet only on the first load as shown below worked as expected, but I am not very confident that it is a good method. If you have a better solution, please let me know.

let firstFlug = false;

    const persistAtom: AtomEffect<any> = ({ onSet, node, trigger, setSelf }) => {
        if (trigger === "get") {
            getState().then((s) => {
                if (s.hasOwnProperty(node.key)) {
                    setSelf(s[node.key]);
                    firstFlug = true;
                } else {
                    if (!firstFlug) firstFlug = true;
                }
            });
        }

        onSet((newValue, _, isReset) => {
            if (firstFlug) {
                if (isReset) {
                    pendingChanges.reset[node.key] = true;
                    delete pendingChanges.updates[node.key];
                } else {
                    pendingChanges.updates[node.key] = newValue;
                }
                if (!pendingChanges.queue) {
                    pendingChanges.queue = getState().then((state) => {
                        if (JSON.stringify(state[node.key]) !== JSON.stringify(newValue)) {
                            updateState(state, pendingChanges);
                        }
                        pendingChanges.queue = null;
                        pendingChanges.reset = {};
                        pendingChanges.updates = {};
                    });
                }
            }
        });
    };