Skip to content

Instantly share code, notes, and snippets.

@mbret
Last active May 12, 2021 20:40
Show Gist options
  • Save mbret/2aa38e9bfa60d1a13d30f3a8e4f336d3 to your computer and use it in GitHub Desktop.
Save mbret/2aa38e9bfa60d1a13d30f3a8e4f336d3 to your computer and use it in GitHub Desktop.
recoil persistance/restoration
import React from 'react'
import { PersistedRecoilRoot } from './PersistedRecoilRoot'
const myState = atom({ key: '...', default: ... })
const statesToPersist = [
myState
]
export const App = () => {
return (
<PersistedRecoilRoot states={statesToPersist}>
...
</PersistedRecoilRoot>
)
}
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>
)
}
@mbret
Copy link
Author

mbret commented Dec 9, 2020

Supported features:

  • async initial state
  • persist / restore atoms
  • convenient interface to reset the states (logout for example)

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:

  • Migration
  • Dynamic atoms
  • Per atom persistance strategy

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/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment