From ddcff57d8bfb1b02edd45c530ec560caf997fa12 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Fri, 18 Feb 2022 19:01:58 +0000 Subject: [PATCH] Add usePersistedStore and useSavedSnapshot - `useSavedSnapshot` loads a store snapshot from storage. - `useSessionStorage` adds an `onSnapshot` listener that persists store snapshots in storage. --- .../src/components/Classifier/hooks/Readme.md | 16 ++++++ .../src/components/Classifier/hooks/index.js | 2 + .../Classifier/hooks/useHydratedStore.js | 53 +++---------------- .../Classifier/hooks/useSavedSnapshot.js | 39 ++++++++++++++ .../Classifier/hooks/useSessionStorage.js | 26 +++++++++ packages/lib-classifier/src/helpers/index.js | 1 - .../src/helpers/persist/index.js | 1 - .../src/helpers/persist/persist.js | 8 --- 8 files changed, 89 insertions(+), 57 deletions(-) create mode 100644 packages/lib-classifier/src/components/Classifier/hooks/useSavedSnapshot.js create mode 100644 packages/lib-classifier/src/components/Classifier/hooks/useSessionStorage.js delete mode 100644 packages/lib-classifier/src/helpers/persist/index.js delete mode 100644 packages/lib-classifier/src/helpers/persist/persist.js diff --git a/packages/lib-classifier/src/components/Classifier/hooks/Readme.md b/packages/lib-classifier/src/components/Classifier/hooks/Readme.md index 8dc846df597..f7888a95799 100644 --- a/packages/lib-classifier/src/components/Classifier/hooks/Readme.md +++ b/packages/lib-classifier/src/components/Classifier/hooks/Readme.md @@ -12,6 +12,22 @@ Uses the `useStore` hook, under the hood, to create a new store from a saved sna const { classifierStore, loaded } = useHydratedStore({ authClient, client }, enableStorage = false, storageKey) ``` +## useSavedSnapshot + +Asynchronously load a store snapshot from session storage. + +```js +const initialState = useSavedSnapshot(enableStorage, storageKey) +``` + +## useSessionStorage + +Wrap a store in an [`onSnapshot`](https://mobx-state-tree.js.org/API/#onsnapshot) listener, which writes snapshots asynchronously to session storage. Returns true when complete, which allows us to wait on store initialisation before adding new data to the store. + +```js +const loaded = useSessionStorage(cachePanoptesData, classifierStore, storageKey) +``` + ## useStore Create a `mobx-state-tree` store, using the Panoptes API clients and an optional snapshot. Adapted from [the NextJS example](https://github.com/vercel/next.js/blob/5201cdbaeaa72b54badc8f929ddc73c09f414dc4/examples/with-mobx-state-tree/store.js#L49-L52), which is also used in `app-project`. `initialState` must be a valid store snapshot. diff --git a/packages/lib-classifier/src/components/Classifier/hooks/index.js b/packages/lib-classifier/src/components/Classifier/hooks/index.js index 1bb1894dd08..cc26d2e6703 100644 --- a/packages/lib-classifier/src/components/Classifier/hooks/index.js +++ b/packages/lib-classifier/src/components/Classifier/hooks/index.js @@ -1,3 +1,5 @@ export { default as useHydratedStore } from './useHydratedStore' +export { default as useSavedSnapshot } from './useSavedSnapshot' +export { default as useSessionStorage } from './useSessionStorage' export { default as useStore } from './useStore' export { default as useWorkflowSnapshot } from './useWorkflowSnapshot' diff --git a/packages/lib-classifier/src/components/Classifier/hooks/useHydratedStore.js b/packages/lib-classifier/src/components/Classifier/hooks/useHydratedStore.js index cf0c5d05107..c34c8b9d419 100644 --- a/packages/lib-classifier/src/components/Classifier/hooks/useHydratedStore.js +++ b/packages/lib-classifier/src/components/Classifier/hooks/useHydratedStore.js @@ -1,53 +1,12 @@ -import { useEffect, useState } from 'react' - -import { asyncSessionStorage, persist } from '@helpers' -import { useStore } from './' - -async function hydrateStore(storageKey) { - let snapshot = {} - try { - snapshot = await loadSnapshot(storageKey, asyncSessionStorage) - console.log('store loaded from local storage') - } catch (error) { - console.log('store snapshot error.') - console.error(error) - } - return snapshot -} - -async function loadSnapshot(storageKey, storage) { - const data = await storage.getItem(storageKey) - const snapshot = JSON.parse(data) - return snapshot -} +import { useSavedSnapshot, useSessionStorage, useStore } from './' export default function useHydratedStore({ authClient, client }, cachePanoptesData = false, storageKey) { - const [initialState, setInitialState] = useState(null) - const [loaded, setLoaded] = useState(false) + // Asynchronously load a store snapshot from an external source. + const initialState = useSavedSnapshot(cachePanoptesData, storageKey) + // Create a new store from the initialState snapshot. const classifierStore = useStore({ authClient, client, initialState }) - - async function onStoreCreated() { - if (!loaded && classifierStore && cachePanoptesData) { - await persist(storageKey, classifierStore, { storage: asyncSessionStorage }) - } - setLoaded(true) - } - - useEffect(() => { - onStoreCreated() - }, [cachePanoptesData, classifierStore, loaded]) - - async function getInitialState() { - let _initialState = {} - if (cachePanoptesData) { - _initialState = await hydrateStore(storageKey) - } - setInitialState(_initialState) - } - - useEffect(() => { - getInitialState() - }, []) + // Write store snapshots to session storage. + const loaded = useSessionStorage(cachePanoptesData, classifierStore, storageKey) return { classifierStore, loaded } } \ No newline at end of file diff --git a/packages/lib-classifier/src/components/Classifier/hooks/useSavedSnapshot.js b/packages/lib-classifier/src/components/Classifier/hooks/useSavedSnapshot.js new file mode 100644 index 00000000000..8a2ffcdf2b8 --- /dev/null +++ b/packages/lib-classifier/src/components/Classifier/hooks/useSavedSnapshot.js @@ -0,0 +1,39 @@ +import { useEffect, useState } from 'react' + +import { asyncSessionStorage } from '@helpers' + +async function loadSnapshot(storageKey, storage) { + const data = await storage.getItem(storageKey) + const snapshot = JSON.parse(data) + return snapshot +} + +async function hydrateStore(storageKey) { + let snapshot = {} + try { + snapshot = await loadSnapshot(storageKey, asyncSessionStorage) + console.log('store loaded from local storage') + } catch (error) { + console.log('store snapshot error.') + console.error(error) + } + return snapshot +} + +export default function useSavedSnapshot(enableStorage, storageKey) { + const [initialState, setInitialState] = useState(null) + + async function getInitialState() { + let _initialState = {} + if (enableStorage) { + _initialState = await hydrateStore(storageKey) + } + setInitialState(_initialState) + } + + useEffect(() => { + getInitialState() + }, [storageKey]) + + return initialState +} \ No newline at end of file diff --git a/packages/lib-classifier/src/components/Classifier/hooks/useSessionStorage.js b/packages/lib-classifier/src/components/Classifier/hooks/useSessionStorage.js new file mode 100644 index 00000000000..a15ffa08745 --- /dev/null +++ b/packages/lib-classifier/src/components/Classifier/hooks/useSessionStorage.js @@ -0,0 +1,26 @@ +import { addDisposer, onSnapshot } from 'mobx-state-tree' +import { useEffect, useState } from 'react' + +import { asyncSessionStorage } from '@helpers' + +function persist(storageKey, store, storage) { + function _saveSnapshot(snapshot) { + const data = JSON.stringify(snapshot) + storage.setItem(storageKey, data) + } + const snapshotDisposer = onSnapshot(store, _saveSnapshot) + return addDisposer(store, snapshotDisposer) +} + +export default function useSessionStorage(enableStorage, store, storageKey) { + const [loaded, setLoaded] = useState(false) + + useEffect(() => { + if (!loaded && store && enableStorage) { + persist(storageKey, store, asyncSessionStorage) + } + setLoaded(true) + }, [enableStorage, store, loaded]) + + return loaded +} \ No newline at end of file diff --git a/packages/lib-classifier/src/helpers/index.js b/packages/lib-classifier/src/helpers/index.js index 6fecfdd357c..5199c426b96 100644 --- a/packages/lib-classifier/src/helpers/index.js +++ b/packages/lib-classifier/src/helpers/index.js @@ -5,7 +5,6 @@ export { featureDetection } export { default as findLocationsByMediaType } from './findLocationsByMediaType' export { default as formatTimeStamp } from './formatTimeStamp' export { default as layouts } from './layouts' -export { default as persist } from './persist' export { default as shownMarks } from './shownMarks' export { default as subjectsSeenThisSession } from './subjectsSeenThisSession' export { default as subjectViewers } from './subjectViewers' diff --git a/packages/lib-classifier/src/helpers/persist/index.js b/packages/lib-classifier/src/helpers/persist/index.js deleted file mode 100644 index 68d8ea09cf4..00000000000 --- a/packages/lib-classifier/src/helpers/persist/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './persist' diff --git a/packages/lib-classifier/src/helpers/persist/persist.js b/packages/lib-classifier/src/helpers/persist/persist.js deleted file mode 100644 index 8e03dd24d8e..00000000000 --- a/packages/lib-classifier/src/helpers/persist/persist.js +++ /dev/null @@ -1,8 +0,0 @@ -import { addDisposer, applySnapshot, onSnapshot } from 'mobx-state-tree' - -export default async function persist(storageKey, store, { storage }) { - addDisposer(store, onSnapshot(store, snapshot => { - const data = JSON.stringify(snapshot) - storage.setItem(storageKey, data) - })) -}