#Best way to lazily create a machine *after* data from hook has loaded

1 messages · Page 1 of 1 (latest)

tawny ermine
#

Hey 👋 We have a lot of React Query hooks for data fetching and we keep running into scenarios in which we need the data returned from these hooks within machines/actors.

I know we can sync data using useEffect and this works wonderfully for things that will also update during the lifecycle of a machine (e.g., because data was mutated outside of it). However, I'd like to avoid the boilerplate if at all possible.

The only solution we've found so far is using a parent/wrapper component that only renders the component which consumes the machine after data has finished loading.

function usePlaygroundMachine() {
  // `data` will be undefined while data is fetching
  const { data } = useSomeData();

  return useMachine(() =>
    createMachine<Context, Event>({
      id: "playground",
      context: {
        // Will also be undefined, of course
        someId: data.someId,
      }
      // ...
    })
  );
}

→ Is there any way to avoid useEffect and wrapper components?

Thank you!

golden perch
tawny ermine
golden perch
#

Oh didnt know they are deprecating that.

#

I guess there isnt any other way then to use useEffect if we want all the magic that react query does.

tawny ermine
#

Okay so best I could find was this: Add an "initializing" state on the root/parent machine that waits until all the required data is available and the transitions into some kind of "ready" state:

createMachine<Context, Event>({
  id: "playground",
  predictableActionArguments: true,
  initial: "init",
  context: () => ({
    companyId: null,
    selectedContact: null,
    allContacts: null,
    createContactActorRef: null,
    editContactActorRef: null,
  }),
  states: {
    /** NOTE: Some values in the context will be undefined in this state. */
    init: {
      type: "parallel",
      onDone: "idle",
      states: {
        waitingForContacts: {
          initial: "waiting",
          states: {
            waiting: {
              on: {
                CONTACTS_CHANGED: {
                  target: "done",
                  actions: assign({
                    contacts: (_, event) => event.contacts,
                  }),
                },
              },
            },
            done: {
              type: "final",
            },
          },
        },
        waitingForCompany: {
          initial: "waiting",
          states: {
            waiting: {
              on: {
                COMPANY_CHANGED: {
                  target: "done",
                  actions: assign({
                    companyId: (_, event) => event.companyId,
                  }),
                },
              },
            },
            done: {
              type: "final",
            },
          },
        },
      },
      exit: assign({
        createContactActorRef: (context) => {
          return (
            context.createContactActorRef ||
            spawn(createContactMachine({ companyId: context.companyId }))
          );
        },
        editContactActorRef: (context) => {
          return context.editContactActorRef || spawn(editContactMachine);
        },
      }),
    },
    idle: {}
  },
});

brave quartz
#

You can also just use the query client directly in XState (easier in v5 though)

#

We're still thinking of ways to let "reactive" values propagate to machines in XState React/Vue/etc

heavy shell
#

That's what i'm doing! if you use fetchQuery instead of useQuery, you can wrap queryClient calls in promise returning services and use onDone and onError

tawny ermine