Search code examples
react-nativeexpoxstate-react

React Native with XState in Global Context causes entire App to reload


I have a React Native Expo App and I am using XState to keep a global state machine that is passed down the component tree using the Context API.

App.js: (only relevant parts shown - e.g. smSend function here is passed in context.

const GlobalXStateMachineContext = createContext({});
 
//Here stateDef and actionDef are state machine and actions
const stateMachine = createMachine(stateDef, actionDef);

export default function App() {

  console.log("APP INVOKED!!");

  const [smState, smSend] = useMachine(stateMachine);
  return (
    <Provider store={store}>
      <GlobalXStateMachineContext.Provider value={{ smSend }}>
        <NavigationContainer>
          <RootScreen />
        </NavigationContainer>
      </GlobalXStateMachineContext.Provider>
    </Provider>
  );
}
export { GlobalXStateMachineContext };

And then somewhere from deep inside the component tree I am getting a hold of smSend from the context and calling it.

Note that everything is working as expected. But whenever the smSend() is called from anywhere in the component tree the App() is called leading to a complete reload of the entire tree!

Notice the console.log("APP INVOKED") is called everytime smSend is called like so.

NestedFunction.js (again only relevant code)

import { GlobalXStateMachineContext } from './App';

const NestedFunction = () => {
  const smSend = useContext(GlobalXStateMachineContext).smSend;
  ....
  smSend({ payload });
}

I am not sure if this is the expected behavior of Context API or XState or a quirk using the two. This is resulting in performance degradation and may lead to hard to find bug.

Is there anyway this full reload of the App may be avoided?


Solution

  • I was able to resolve this issue. This was a documented behavior in XState/react https://xstate.js.org/docs/recipes/react.html#improving-performance

    Instead of the useMachine hook, I switched to useInterpret as described in the docs above.

    import { useInterpret } from '@xstate/react';
    const scoreService = useInterpret(stateMachine);
    
    ...
    return (
    <Provider store={store}>
      <GlobalXStateMachineContext.Provider value={{ scoreService }}>
        <RootScreen />
      </GlobalXStateMachineContext.Provider>
    </Provider>
    );
    ...
    export { GlobalXStateMachineContext };
    

    And NestedFunction.js (again only relevant code)

    const { send } = useContext(GlobalXStateMachineContext).scoreService;
    ....
    send({ payload });