When reinitializing state using initializeState on RecoilRoot it appears that calling set does not trigger hooks, so my app is not updated.
In the image below you can see:
initializeState being run first ("Init state")useRecoilValue - this is the default valueset is called inside initializeState but useRecoilValue isn't triggered again
I'm using Recoil with React Native based on #114 - perhaps this is related to that?
This is the code I use to persist my atoms:
export default function createRecoilPersistor(
atoms: Array<RecoilState<any>>
): Persistors {
function initializeState({ set }: Initializer): void {
console.log('Init state');
for (const currentAtom of atoms) {
const { key } = currentAtom;
AsyncStorage.getItem(key).then(value => {
if (value !== null) {
console.log('Set value for ' + currentAtom.key, { value });
set(currentAtom, JSON.parse(value));
}
});
}
}
function RecoilPersistor(): null {
useTransactionObservation_UNSTABLE(
({ atomValues, modifiedAtoms }: Observation): void => {
for (const modifiedAtom of modifiedAtoms) {
const matchingAtom = atoms.find(({ key }) => key === modifiedAtom);
if (matchingAtom) {
AsyncStorage.setItem(
modifiedAtom,
JSON.stringify(atomValues.get(modifiedAtom))
);
}
}
}
);
return null;
}
return {
initializeState,
RecoilPersistor
};
}
Then in my App.js:
const { RecoilPersistor, initializeState } = createRecoilPersistor([
darkModeAtom,
userAuthAtom,
userDetailsAtom
]);
function useTheme() {
const darkMode = useRecoilValue(darkModeAtom);
console.log({ initDarkMode: darkMode });
const baseTheme = darkMode ? eva.dark : eva.light;
const statusBarStyle = darkMode ? 'light-content' : 'default';
return [{ ...baseTheme, ...customTheme }, statusBarStyle];
}
function App(): React.ReactElement {
const [theme, statusBarStyle] = useTheme();
return (
<ApplicationProvider {...eva} theme={theme}>
<StatusBar barStyle={statusBarStyle} />
<NavigationRoot />
</ApplicationProvider>
);
}
export default function AppRoot(): React.ReactElement {
return (
<>
<IconRegistry icons={EvaIconsPack} />
<SafeAreaProvider>
<RecoilRoot initializeState={initializeState}>
<RecoilPersistor />
<App />
</RecoilRoot>
</SafeAreaProvider>
</>
);
}
For anyone else waiting for the finished API, you can gate your application while you initialise your atoms manually:
export function useRecoilPersistStorage({
atom,
storage
}: RecoilPersistStorageHook): [boolean, any] {
const [loaded, setLoaded] = useState(false);
const [atomValue, setAtomValue] = useRecoilState(atom);
useEffect(() => {
storage.get().then(value => {
setLoaded(true);
if (value !== null) {
setAtomValue(value);
}
});
}, []);
return [loaded, atomValue];
}
export function RecoilStorageGate({
atom,
storage,
children
}: RecoilStorageGateProps): React.ReactElement {
const [loaded] = useRecoilPersistStorage({ atom, storage });
if (!loaded) {
return (
<View style={style.splash}>
<Text>Splash screen</Text>
</View>
);
}
return <>{children}</>;
}
I'm too stucked with this issue. initializeState used to work with verion 0.0.8 but with version 0.0.10, it has stopped working
The initializeState prop is only intended to setup the initial state before the initial render. This is useful for supporting things like server-side rendering where it is critical the state is hydrated for that initial render. It is not intended for async updates to state. Use the existing Recoil hooks for updating state asynchronously, such as useSetRecoilState() or useRecoilCallback()
Thanks for the clarification and quick update to the docs 馃憤
Are there plans to allow for async initialisation in future?
I don't know that we'd want to complicate the core interface with this, but some helper wrapper util to support async initialization may make sense. Haven't tested the below, but maybe something like this... Let us know if it works for you.
function AsyncInitRecoilRoot({children, Fallback, initializeStateAsync}) {
const [isLoaded, setLoaded] = useState(false);
function InitializeState() {
const snapshot = useRecoilSnapshot();
const gotoSnapshot = useGotoRecoilSnapshot();
useEffect(async () => {
const initializedSnapshot = await snapshot.mapAsync(initializeStateAsync);
gotoSnapshot(initializedSnapshot);
setLoaded(true);
}, []);
return null;
}
return (
<RecoilRoot>
{isLoaded
? children
: <>
<InitializeState />
<Fallback />
</>
}
</RecoilRoot>
);
}
Most helpful comment
The
initializeStateprop is only intended to setup the initial state before the initial render. This is useful for supporting things like server-side rendering where it is critical the state is hydrated for that initial render. It is not intended for async updates to state. Use the existing Recoil hooks for updating state asynchronously, such asuseSetRecoilState()oruseRecoilCallback()