Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notify when state has rehydrated (with async storage) #20

Open
BenJeau opened this issue Feb 5, 2021 · 12 comments
Open

Notify when state has rehydrated (with async storage) #20

BenJeau opened this issue Feb 5, 2021 · 12 comments

Comments

@BenJeau
Copy link

BenJeau commented Feb 5, 2021

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.

@rhys-saldanha
Copy link
Contributor

which version of recoil-persist are you using?

@BenJeau
Copy link
Author

BenJeau commented Feb 5, 2021

I'm using the latest version, 2.1.0

@rhys-saldanha
Copy link
Contributor

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.

@BenJeau
Copy link
Author

BenJeau commented Feb 6, 2021

@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

@rhys-saldanha
Copy link
Contributor

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

@rhys-saldanha
Copy link
Contributor

@RobertSasak
Copy link

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>
    )
}

@dezudas
Copy link

dezudas commented Jul 12, 2021

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

@RobertSasak
Copy link

No. This is the official way.

@TwistedMinda
Copy link

TwistedMinda commented May 15, 2022

@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

@RobertSasak
Copy link

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

@TwistedMinda
Copy link

@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 ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants