polemius / recoil-persist

Package for recoil state manager to persist and rehydrate store

Home Page:https://polemius.dev/recoil-persist/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Notify when state has rehydrated (with async storage)

BenJeau opened this issue · comments

Since asynchronous storage is supported, when the app opens, the default value of the atom is returned, then its persisted value is returned shortly afterwards. For example, in my mobile application, I have a disclaimer screen that is shown once when the app is first open, then it is never shown again, but since the default state is returned, it always shows the disclaimer. After simply putting a console.log in the useEffect() on that atom state, I see the following:

image

How could the situation be handled? I thought of putting a setTimeout() and show a "loading" component, but I feel like there must be a better way. I am coming from redux-persist and they render a "loading" component while the state rehydrates using the <PersistGate/> component.

which version of recoil-persist are you using?

I'm using the latest version, 2.1.0

Looking at AsyncStorage, it seems to be a wrapper around window.localStorage - have you tried passing that to recoil-persist instead?

Separately, I've been investigating the async use case, and I think the solution might be to support recoil Selectors, which are made for things like API calls. The key part here is that they would add React Suspense / a pending boolean with very little effort on our side.

@rhys-saldanha yes, I should probably be clearer. I am using React Native with AsyncStorage as the storage recoil-persist. AsyncStorage is basically an async implementation of the Storage API which saves the data to SQLite on Android and something else on iOS since localStorage doesn't exist on those devices.

I think using React Suspense would not be a bad idea, but I'm not sure how to implement that

My bad, I was reading the React Native source, saw window.localStorage, and assumed all platforms would be the same... I take that back!

I combined your wisdom into following code:

import { atom, AtomEffect, selector, DefaultValue } from 'recoil'
import AsyncStorage from '@react-native-async-storage/async-storage'

const persistAtom: AtomEffect<any> = ({ node, setSelf, onSet }) => {
    setSelf(
        AsyncStorage.getItem(node.key).then((savedValue) =>
            savedValue != null ? JSON.parse(savedValue) : new DefaultValue(),
        ),
    )

    onSet((newValue) => {
        if (newValue instanceof DefaultValue) {
            AsyncStorage.removeItem(node.key)
        } else {
            AsyncStorage.setItem(node.key, JSON.stringify(newValue))
        }
    })
}

export const nameState = atom({
    key: 'name',
    default: '',
    effects_UNSTABLE: [persistAtom],
})

And then wrapped my app in .

const App = () => {
    return (
        <RecoilRoot>
            <React.Suspense fallback={<Text>Loading</Text>}>
                <Navigation />
            </React.Suspense>
        </RecoilRoot>
    )
}

@RobertSasak than you for the solution, have you come up with any better way?

No. This is the official way.

@RobertSasak Hello! One year later is it still the preferred way? Because I can't get it working personally.
Because I have multiple atoms that I need to have preloaded before React.Suspense allows rendering, I think it's causing an issue. The Suspense allows rendering right after the first atom was loaded from storage, leaving me with unpredictable state for at least one of the "early needed" atoms.

Any workaround for such a case?
Thanks

@TwistedMinda You may need to provide a snipet of the code to better understand the issue. Perhaps even open a new issue.

@RobertSasak Yes I'm sorry I was only in the learning process, and decided finally to switch to jotai for my final choice. Don't have the codebase to reproduce the bug anymore. Should have placed details directly!

I think the problem I encountered with recoil was a bit specific : I think I was trying to store a function in the AsyncStorage, which led to a very unpredictable bug. Does that seem legit? Can that lead to wrongly loaded store. I remember my objects looking like after being loaded from async storage:

{"r": "o", "k": "b", 5: 4, "myNormallyExpectedVar": "myNormallyExpectedValue"}

You will note the incomprehensible keys at the start ;)