zustand-persist
zustand-persist copied to clipboard
Usage with expo
Hi, is it meant to work with Expo ?
I'm trying with the last Expo (sdk 39) but the app is stuck on the Persist Gate which only display the loading component.
thanks
Hi, could you create a basic repository so that I can investigate it?
So I'm also getting this its very weird - when I run expo and remotely debug its totally fine, but when I disable remote debugging it stops working and refuses to load.
Will Attempt to set up a repo at some point today
So I've scratched up a https://github.com/Trashpants/zustand-demo-expo Hopefully this helps get to the bottom of this.
I'm having the same problem without Expo.
@roadmanfong Anything we can do to help getting this resolved?
It appears that the white screen is actually a stuck loading screen. You can test by providing <PersistGate>
with a loading
prop.
<PersistGate
loading={
<View>
<BaseText>Loading</BaseText>
</View>
}
>
// App content
</PersistGate>
);
I haven't dug further, I don't know that I have the time to.
Yeah it seems to not get past the loading, which points to the fact it’s not rehyrdating or not completing the rehydration process.
@Trashpants I think I got it to work. A key part of the hydration happening is calling the store within your main App structure. Below I have a partial from my App.tsx
file and my store.ts
. In store.ts
I'm instantiating a zustand store utilizing configurePersist
zustand-persist. Then in App.tsx
I call the hook.
//store.ts
const { persist, purge } = configurePersist({
storage: AsyncStorage,
rootKey: 'root'
});
export const useStore = create(
persist(
{
key: 'data'
},
set => ({
count: 0,
method: () => set((state: any) => ({ count: state.count + 1 }))
})
)
);
//App.tsx
const App: () => JSX.Element = () => {
const { data } = useStore();
return (
<PersistGate
loading={
<View>
<BaseText>Loading</BaseText>
</View>
}
>
// App content
</PersistGate>
);
};
I was very confused when installing the library that no configuration parameters were passed to PersistGate. This answers why.
@callmetwan 's solution was exactly what was needed - making a call to some kind of state in the same screen as <PersistGate>
is what was needed
For anyone looking at this or confused the magic line in @callmetwan solution is:
const { data } = useStore();
within App.tsx
@Trashpants While not strictly related to this topic it is worth noting that you cannot use any non-stringable types in your store if using this zustand-persist. The reason being that localStorage and AsyncStorage serialize their values (require them to be strings). This may or may not have practical limitations on how you build your store.
For instance, any actions you create must exist during bootstrapping. If you tried to add them using useStore.setState({newAction: () => console.log('hi')})
they will exist while the app is in memory but they will not be persisted. This also means you cannot use types like Map or Set.
I was already intending to use whichever state solution I decided on in the with AsyncStorage so I'm not losing much, but others reading should be aware. Just to be clear, this isn't a limitation of this library, this is a limitation of localStorage and AsyncStorage.
hmmm I've just got around to trying with iOS and again I'm hitting the thing I had before which is very weird - unless im debugging it doesn't want to get past loading - what's super strange is that I can update the store within the loading section:
/**
* font loading stuff
*/
const { onboardingComplete, toggleOnboardingComplete } = useSettingsStore();
return (
<PersistGate
loading={
<View
style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}
>
<Text
onPress={() => {
toggleOnboardingComplete();
}}
>
APP HAS LOADED: {onboardingComplete + ''}, fonts loaded:
{loaded + ''},
</Text>
</View>
}
>
{loaded && onboardingComplete && <RootNavigator />}
</PersistGate>
Very weird. For a moment that happened to me, but then I stopped/restarted the metro bundler and it all cleared up. I'm a bit busy today but I'd be interested in looking into this with you; I'm planning on using this library for a project and once I make a decision I won't have time to reverse it, so discovering and solving problems like these is important to me.
const { data } = useStore();
within
App.tsx
Hmm, this does nothing for me. I'm still having the same issue.
Out of interest, iOS or Android?
const { data } = useStore();
withinApp.tsx
Hmm, this does nothing for me. I'm still having the same issue.
what happens when you do remote debugging (assuming you're using expo)? Thats what's really leaving me scratching my head - when I have it enabled things work as expected (on iOS, android seems to be totally fine with and without remote debugging)
iOS 14.1, simulator, not Expo.
PersistGate seems to open fine when debug mode is enabled.
Anyone have any ideas where to start digging to fix this issue - I've been asked to use zustand in a project as other devs are comfortable using it, but I definitely need a persistence layer for RN.
especially as its impossible to actually console.log bits of data out for this anyone got any ideas of where to begin poking?
Obviously the isReady
value inside the PersistGate file is always returning false, which makes sense but I'm struggling to get past that
I don’t have an immediate suggestion for that, but what I did to test was copy the source files to my project and import them from there instead of the npm package. This will allow you to debug where the failure is happening.
okay so looking directly in the source code in node_modules:
index.js line 130
changing it from
return React__default['default'].createElement(React__default['default'].Fragment, null, isReady ? children : loading);
to
return React__default['default'].createElement(React__default['default'].Fragment, null, isReady ? children : loading(getLoadManager().onAllLoadedCallback()));
Gets me around it, which points me to thinking that the function just isn't being re-run as values change and firing it again (presumably after the data is updated) forces that re-render?
okay sorry to spam up the thread but it looks like the onAllLoaded function just flat out doesn't run. I'm not sure why it doesn't as the LoadManager.setLoaded
function works as its possible to watch loadStatusRecord
get updated.
Im not sure what happens but it appears that onAllLoaded is entirely failing to run or the method acts like its empty?
Not spamming, this is helpful! I'll try to take a look at it tonight to see if I can add anything useful.
I've done what you have suggested and copied the code into my project directly. Now the project never loads which seems weird but what I think is happening is as follows:
configurePersist
creates a new LoadManager
when trying to hydrate and once its complete it SHOULD then do an onAllLoadedCallback
, however that's not set within the instance because.....
PersistGate
is trying to get access to the SAME LoadManager
instance where it should instantly set the onAllLoadedCallback
. However that's not happening.
in LoadManager
update line 6 to be
this.onAllLoadedCallback = () => {
console.log('INITIAL VERSION OF CALLBACK');
};
And it will only be called once where from what I understand it should be called twice - technically once with the default callback and then once again once the onAllLoaded
has been set within PersistGate
edit:
Ignore this comment im being a dope - made a typo in my app so it wasn't actually using the persist gate properly
okay so, back to the original point I had, adding this inside the main body of PersistGate
fixes the issue - ie forces it to run again.
(add it in at line 15)
useEffect(() => {
getLoadManager().setLoaded('');
}, []);
the whole PeristGate file looks like this:
import React, { useEffect, useState } from 'react';
import { getLoadManager } from './LoadManager';
export interface PersistGateProps {
children?: React.ReactNode;
loading?: React.ReactNode;
onBeforeLift?: () => void;
}
export function PersistGate(props: PersistGateProps): JSX.Element {
const { children, loading = false, onBeforeLift } = props;
const [isReady, setIsReady] = useState(false);
useEffect(() => {
getLoadManager().setLoaded('');
}, []);
getLoadManager().onAllLoaded(() => {
onBeforeLift && onBeforeLift();
setIsReady(true);
});
return <React.Fragment>{isReady ? children : loading}</React.Fragment>;
}
I think this points to it being a race condition, I assume when we're debugging PeristGate loads first, then when the hydrate runs it fires the setLoaded up, when running without debugging I assume the other way around
edit:
Im pretty much stuck at this point, I know adding this in forces it to work for me. However it definitely feels like a total hack. @roadmanfong any ideas why re-running setLoaded
that would get around the issue?
Hi, is there any progress on this issue? We are also stuck at this point..
hi everybody! Do you have any news about this issue? I really want to use zustand but without Persist layer on React Native I'd go with redux and redux-persist.
As it currently stands I’m still running with that hack above and I’ve not come across any other issues with it.
has anyone else tried that out and seen if it solves the issue for them?
+1 for the useEffect workaround working for me. @roadmanfong would you be open to a PR even though this would be a workaround? Would make the library feel more predictable.
Any pr are welcome here, I'm afraid I don't have time to implemented right away.
I think this might effectively be a duplicate of #9 as well. Will take a look at both and see what can be done.