0

I'm working on a UI project which handles state updates through a shared context, very similar as described here

const {appState, dispatch} = useContext(AppContext);

I'm not toying around with state machines through xstate for some of the components.

const SomeComponent = () => {
  const {appState, dispatch} = useContext(AppContext);
  const [state,send,service] = useMachine(MyComponentStateMachine);
}

Now, ideally I would like my state machine to dispatch certain events when entering a state. What's the best way for the state machine to get a hold of my AppContext, though?

At the moment, I'm handling this event dispatching on the component itself, observing the state of the state machine and dispatching an event as needed when it enters a certain state:

const SomeComponent = () => {
  const {appState, dispatch} = useContext(AppContext);
  const [state,send,service] = useMachine(MyComponentStateMachine);

  useEffect(() => service.subscribe(state => {
     if(state.value == "Some relevant state of MyComponentStateMachine")
       dispatch({type: SomeEvent, arg: 12345});  
    }).unsubscribe, [service]);
}

This works well, but it strikes me as bad design. I'd think it would be cleaner to dispatch this event from the state machine directly, rather than from the component.

Is there any good way for the state machine to get a hold of AppContext?

Would it be sensible to simply create a factory method for the state machine which takes dispatch as an argument, and holds on to it?

Bogey
  • 4,926
  • 4
  • 32
  • 57

1 Answers1

0

I believe there's nothing wrong to call your dispatch function there. Due that you are using context, you wouldn't be able to call the dispatch inside the machine, unless you pass the function as a parameter. You can try that, not sure it that would work.

(You can use actions to trigger side effects on events or state changes)

In that case it would be something like this:

<pre>
  //Component
  const SomeComponent = () => {
    const { appState, dispatch } = useContext(AppContext);
    const [ state, send ] = useMachine(MyComponentStateMachine);

    useEffect(() => {
      if(state.matches("stateName")) {
        const data = { type: SomeEvent, arg: 12345 };
        send("EVENT_NAME", { callback: dispatch, data })
      }
    }, [state]);
  }


  //Machine
  const MyComponentStateMachine = Machine({
    ...
    states: {
      stateName: {
        on: {
          EVENT_NAME: "secondState",
        },
      },
      secondState: {
        entry: ["actionName"]
      }
    },
    {
      actions: {
        actionName: (ctx, e) => {
          e.callback(e.data);
        },
      }
    }
  });
</pre>

*** Also look how I compare the state, that is a cleaner way to read the state value. Also you don't need to subscribe to the service if you are already using the useMachine hook, the hook will trigger a component rerender if the state changes.

Rodrigo
  • 740
  • 5
  • 7