I want to enable persistance for react-native application.
Following tutorial on https://garden.bradwoods.io/notes/javascript/state-management/xstate/global-state#rehydratestate
I can't use asynchronous code inside xstate's hook useInterpret
Original code (which uses localStorage instead of AsyncStorage) doesn't have that issue since localStorage is synchronous.
import AsyncStorage from '@react-native-async-storage/async-storage';
import { createMachine } from 'xstate';
import { createContext } from 'react';
import { InterpreterFrom } from 'xstate';
import { useInterpret } from '@xstate/react';
export const promiseMachine = createMachine({
id: 'promise',
initial: 'pending',
states: {
pending: {
on: {
RESOLVE: { target: 'resolved' },
REJECT: { target: 'rejected' },
},
},
resolved: {},
rejected: {},
},
tsTypes: {} as import('./useGlobalMachine.typegen').Typegen0,
schema: {
events: {} as { type: 'RESOLVE' } | { type: 'REJECT' },
},
predictableActionArguments: true,
});
export const GlobalStateContext = createContext({
promiseService: {} as InterpreterFrom<typeof promiseMachine>,
});
const PERSISTANCE_KEY = 'test_key';
export const GlobalStateProvider = (props) => {
const rehydrateState = async () => {
return (
JSON.parse(await AsyncStorage.getItem(PERSISTANCE_KEY)) ||
(promiseMachine.initialState as unknown as typeof promiseMachine)
);
};
const promiseService = useInterpret(
promiseMachine,
{
state: await rehydrateState(), // ERROR: 'await' expressions are only allowed within async functions and at the top levels of modules.
},
(state) => AsyncStorage.setItem(PERSISTANCE_KEY, JSON.stringify(state))
);
return (
<GlobalStateContext.Provider value={{ promiseService }}>
{props.children}
</GlobalStateContext.Provider>
);
};
I tried to use .then
syntax to initialize after execution of async function but it caused issue with conditional rendering of hooks.
I had the same use case recently and from what I found there is no native way for xState to handle the async request. What is usually recommended is to introduce a generic wrapper component that takes the state from the AsyncStorage and pass it a prop to where it is needed.
In your App.tsx
you can do something like:
const [promiseMachineState, setPromiseMachineState] = useState<string | null>(null);
useEffect(() => {
async function getPromiseMachineState() {
const state = await AsyncStorage.getItem("test_key");
setPromiseMachineState(state);
}
getAppMachineState();
}, []);
return (
promiseMachineState && (
<AppProvider promiseMachineState={promiseMachineState}>
...
</AppProvider>
)
)
And then in your global context you can just consume the passed state:
export const GlobalStateProvider = (props) => {
const promiseService = useInterpret(
promiseMachine,
{
state: JSON.parse(props.promiseMachineState)
},
(state) => AsyncStorage.setItem(PERSISTANCE_KEY, JSON.stringify(state))
);
return (
<GlobalStateContext.Provider value={{ promiseService }}>
{props.children}
</GlobalStateContext.Provider>
);
};