From 4a83dfdd6fb8947aa831090f0e2beb0066fbd6f4 Mon Sep 17 00:00:00 2001 From: Matt Sain Date: Thu, 17 Feb 2022 19:49:38 +1100 Subject: [PATCH 1/3] fix: allow getItem promise --- src/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 45d1871..9d57e46 100644 --- a/src/index.ts +++ b/src/index.ts @@ -84,12 +84,12 @@ export const recoilPersist = ( return {} } - const parseState = (state: string) => { - if (state === undefined) { + const parseState = (toParse: string) => { + if (toParse === null || toParse === undefined) { return {} } try { - return JSON.parse(state) + return JSON.parse(toParse) } catch (e) { console.error(e) return {} From 40406481a55055e595309db4b3bfe1592dd2ac29 Mon Sep 17 00:00:00 2001 From: Matt Sain Date: Thu, 17 Feb 2022 19:50:51 +1100 Subject: [PATCH 2/3] fix: type enforcement --- src/index.ts | 66 ++++++++++++++++++++-------------------------------- 1 file changed, 25 insertions(+), 41 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9d57e46..c2ef563 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,18 @@ import { AtomEffect } from 'recoil' +type PersistItemValue = string | undefined | null + export interface PersistStorage { - setItem(key: string, value: string): void | Promise - mergeItem?(key: string, value: string): Promise - getItem(key: string): null | string | Promise + setItem(key: string, value: string): any + mergeItem?(key: string, value: string): any + getItem(key: string): PersistItemValue | Promise } +export interface PersistState {} + export interface PersistConfiguration { - key?: string - storage?: PersistStorage + key: string + storage: PersistStorage } /** @@ -19,7 +23,7 @@ export interface PersistConfiguration { * @param config.storage Local storage to use, defaults to `localStorage` */ export const recoilPersist = ( - config: PersistConfiguration = {}, + config: Partial = {}, ): { persistAtom: AtomEffect } => { if (typeof window === 'undefined') { return { @@ -27,30 +31,22 @@ export const recoilPersist = ( } } - const { key = 'recoil-persist', storage = localStorage } = config + const { + key = 'recoil-persist' as PersistConfiguration['key'], + storage = localStorage as PersistConfiguration['storage'], + } = config const persistAtom: AtomEffect = ({ onSet, node, trigger, setSelf }) => { if (trigger === 'get') { - const state = getState() - if (typeof state.then === 'function') { - state.then((s) => { - if (s.hasOwnProperty(node.key)) { - setSelf(s[node.key]) - } - }) - } - if (state.hasOwnProperty(node.key)) { - setSelf(state[node.key]) - } + getState().then((s) => { + if (s.hasOwnProperty(node.key)) { + setSelf(s[node.key]) + } + }) } onSet(async (newValue, _, isReset) => { - const state = getState() - if (typeof state.then === 'function') { - state.then((s: any) => updateState(newValue, s, node.key, isReset)) - } else { - updateState(newValue, state, node.key, isReset) - } + getState().then((s) => updateState(newValue, s, node.key, isReset)) }) } @@ -69,22 +65,7 @@ export const recoilPersist = ( setState(state) } - const getState = (): any => { - const toParse = storage.getItem(key) - if (toParse === null || toParse === undefined) { - return {} - } - if (typeof toParse === 'string') { - return parseState(toParse) - } - if (typeof toParse.then === 'function') { - return toParse.then(parseState) - } - - return {} - } - - const parseState = (toParse: string) => { + const parseState = (toParse: string | null | undefined): PersistState => { if (toParse === null || toParse === undefined) { return {} } @@ -96,7 +77,10 @@ export const recoilPersist = ( } } - const setState = (state: any): void => { + const getState = (): Promise => + Promise.resolve(storage.getItem(key)).then(parseState) + + const setState = (state: PersistState): void => { try { if (typeof storage.mergeItem === 'function') { storage.mergeItem(key, JSON.stringify(state)) From 08113191b1ec5517269fbaa11da485eca4fe2d0c Mon Sep 17 00:00:00 2001 From: Matt Sain Date: Thu, 24 Feb 2022 11:59:44 +1100 Subject: [PATCH 3/3] fix: multi set --- src/index.ts | 48 ++++++++++++++++++++++++++++++++------------- test/index.spec.tsx | 7 ++++--- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/index.ts b/src/index.ts index c2ef563..33b8856 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,12 @@ export interface PersistConfiguration { storage: PersistStorage } +interface PendingChanges { + queue: Promise | null + updates: Partial + reset: Record +} + /** * Recoil module to persist state to storage * @@ -36,6 +42,12 @@ export const recoilPersist = ( storage = localStorage as PersistConfiguration['storage'], } = config + const pendingChanges: PendingChanges = { + queue: null, + updates: {}, + reset: {}, + } + const persistAtom: AtomEffect = ({ onSet, node, trigger, setSelf }) => { if (trigger === 'get') { getState().then((s) => { @@ -45,27 +57,35 @@ export const recoilPersist = ( }) } - onSet(async (newValue, _, isReset) => { - getState().then((s) => updateState(newValue, s, node.key, isReset)) + onSet((newValue, _, isReset) => { + 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) => { + updateState(state, pendingChanges) + pendingChanges.queue = null + pendingChanges.reset = {} + pendingChanges.updates = {} + }) + } }) } - const updateState = ( - newValue: any, - state: any, - key: string, - isReset: boolean, - ) => { - if (isReset) { + const updateState = (state: PersistState, changes: PendingChanges) => { + Object.keys(changes.reset).forEach((key) => { delete state[key] - } else { - state[key] = newValue - } - + }) + Object.keys(changes.updates).forEach((key) => { + state[key] = changes.updates[key] + }) setState(state) } - const parseState = (toParse: string | null | undefined): PersistState => { + const parseState = (toParse: PersistItemValue): PersistState => { if (toParse === null || toParse === undefined) { return {} } diff --git a/test/index.spec.tsx b/test/index.spec.tsx index a7b7bcb..8318b97 100644 --- a/test/index.spec.tsx +++ b/test/index.spec.tsx @@ -25,7 +25,7 @@ const asyncStorage = (): TestableStorage => { setItem: (key: string, value: string) => { return new Promise((resolve) => { s[key] = value - resolve() + resolve(undefined) }) }, getItem: (key: string): Promise => { @@ -103,7 +103,8 @@ function testPersistWith(storage: TestableStorage) { const updateMultiple = useRecoilCallback(({ set }) => () => { set(counterState, 10) set(counterFamily('2'), 10) - }) + }, []); + return (

{count}

@@ -382,7 +383,7 @@ function testPersistWith(storage: TestableStorage) { ) }) - it.skip('should handle updating multiple atomes', async () => { + it('should handle updating multiple atoms', async () => { const { getByTestId } = render(