Last active
May 12, 2021 20:40
-
-
Save mbret/2aa38e9bfa60d1a13d30f3a8e4f336d3 to your computer and use it in GitHub Desktop.
recoil persistance/restoration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import { PersistedRecoilRoot } from './PersistedRecoilRoot' | |
const myState = atom({ key: '...', default: ... }) | |
const statesToPersist = [ | |
myState | |
] | |
export const App = () => { | |
return ( | |
<PersistedRecoilRoot states={statesToPersist}> | |
... | |
</PersistedRecoilRoot> | |
) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useRecoilTransactionObserver_UNSTABLE, RecoilRoot, useRecoilCallback, RecoilState, MutableSnapshot } from "recoil"; | |
import localforage from 'localforage' | |
import React, { createContext, FC, useContext, useEffect, useRef, useState } from "react"; | |
import { Subject, asyncScheduler } from "rxjs"; | |
import { throttleTime } from 'rxjs/operators' | |
const PersistedStatesContext = createContext<RecoilState<any>[]>([]) | |
const usePersistance = () => { | |
const statesToPersist = useContext(PersistedStatesContext) | |
const subject = useRef(new Subject<{ [key: string]: any }>()) | |
useRecoilTransactionObserver_UNSTABLE(({ snapshot }) => { | |
let newState = {} | |
for (const modifiedAtom of snapshot.getNodes_UNSTABLE({ isModified: true })) { | |
// only persist the wanted state | |
if (!statesToPersist.find(state => state.key === modifiedAtom.key)) return | |
const atomLoadable = snapshot.getLoadable(modifiedAtom); | |
if (atomLoadable.state === 'hasValue') { | |
newState[modifiedAtom.key] = { value: atomLoadable.contents } | |
} | |
} | |
subject.current.next(newState) | |
}); | |
useEffect(() => { | |
const listener$ = subject.current | |
.pipe(throttleTime(500, asyncScheduler, { trailing: true })) | |
.subscribe(async (newState) => { | |
try { | |
const prevValue = await localforage.getItem<string>(`local-user`) | |
localforage.setItem( | |
`local-user`, | |
JSON.stringify({ | |
...prevValue ? JSON.parse(prevValue) : {}, | |
...newState | |
}) | |
) | |
} catch (e) { | |
console.error(e) | |
} | |
}) | |
return () => listener$.unsubscribe() | |
}, []) | |
} | |
export const useResetStore = () => { | |
const statesToReset = useContext(PersistedStatesContext) | |
return useRecoilCallback(({ reset }) => async () => { | |
statesToReset.forEach(key => { | |
reset(key) | |
}) | |
// force delete right awayt | |
await localforage.setItem(`local-user`, JSON.stringify({})) | |
}) | |
} | |
const RecoilPersistor = () => { | |
usePersistance() | |
return null | |
} | |
export const PersistedRecoilRoot: FC<{ states?: RecoilState<any>[] }> = ({ children, states = [] }) => { | |
const [initialeState, setInitialState] = useState<{ [key: string]: { value: any } } | undefined>(undefined) | |
useEffect(() => { | |
(async () => { | |
const restored = await localforage.getItem<string>(`local-user`) | |
setInitialState(restored ? JSON.parse(restored) : {}) | |
})() | |
}, []) | |
const initializeState = ({ set }: MutableSnapshot) => { | |
if (initialeState) { | |
Object.keys(initialeState || {}).forEach((key) => { | |
const stateToRestore = states.find(state => state.key === key) | |
if (stateToRestore) { | |
set(stateToRestore, initialeState[key].value); | |
} | |
}) | |
} | |
} | |
return ( | |
<PersistedStatesContext.Provider value={states}> | |
{initialeState ? ( | |
<RecoilRoot initializeState={initializeState}> | |
<RecoilPersistor /> | |
{children} | |
</RecoilRoot> | |
) : null} | |
</PersistedStatesContext.Provider> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Supported features:
This is the code I use for my app so it's not very generic at the moment (rxdb, localforage), you can very easily extend it to use a different storage interface. You can also change rxdb and implement your own persist process.
Unsupported features at the moment:
I will update it once I am done with it.
Keep in mind that this code is "temporary" since it is using experimental features and a much better solution could be released any time by recoil team. That being said, it just work for me in the meantime
@see https://recoil.docschina.org/docs/guides/persistence/