Awaiting asynchronous params when using xstate `useInterpret`

396 Views Asked by At

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.

1

There are 1 best solutions below

0
On BEST ANSWER

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