1

I was exploring the XState world and tried to recreate the machine @davidkpiano mentioned in this talk

And faced an issue with doing the transitions right. When I send a message to parent machine, it resets all the child machines.

For example: I expect the machine to be in dirty and focused state after I fire the CHANGE and FOCUS event one after another. But after sending FOCUS message the pristine state resets back to pristine.

I found this issue, but multiple transitions do the same thing (as it actually described in the issue itself)

Also, I don't want to store all the information about pristine, focused and touched state into the context, because it will not be as safe as doing it with state machines.

The code below is copy-pastable into https://xstate.js.org/viz/

const createPristineMachineConfig = (inputName) => {
  return {
    id: `${inputName}.pristine`,
    initial: 'pristine',
    states: {
      pristine: {
        on: {
          [`${inputName}.CHANGE`]: 'dirty',
        },
      },
      dirty: {
        type: 'final',
      },
    },
  };
};

const createTouchedConfig = (inputName) => {
  return {
    id: `${inputName}.touched`,
    initial: 'untouched',
    states: {
      untouched: {
        on: {
          [`${inputName}.TOUCH`]: 'touched',
        },
      },
      touched: {
        type: 'final',
      },
    },
  };
};

const createFocusedMachineConfig = (inputName) => {
  return {
    id: `${inputName}.focused`,
    initial: 'blurred',
    states: {
      blurred: {
        on: {
          [`${inputName}.FOCUS`]: 'focused',
        },
      },
      focused: {
        on: {
          [`${inputName}.BLUR`]: 'blurred',
        },
      },
    },
  };
};

const createInputMachineConfig = (inputName) => ({
  id: inputName,
  type: 'parallel',
  context: {
    value: '',
  },
  on: {
    FOCUS: {
      actions: send(`${inputName}.FOCUS`),
      internal: true,
    },
    BLUR: {
      actions: [send(`${inputName}.TOUCH`), send(`${inputName}.BLUR`)],
      internal: true,
    },
    CHANGE: {
      actions: [assign((ctx, event) => ({ ...ctx, value: event.payload.value })), send(`${inputName}.CHANGE`)],
      internal: true,
    },
  },
  states: {
    pristine: createPristineMachineConfig(inputName),
    touched: createTouchedConfig(inputName),
    focused: createFocusedMachineConfig(inputName),
  },
});

const loginInputMachine = Machine(createInputMachineConfig('login'));
Temoncher
  • 644
  • 5
  • 15
  • Couldn't solve it myself, but here are some ideas that you could try out: As you already saw in the GH issue the parallel states are probably reset with their initial state when the whole machine receives an event. You could give history states a try. And maybe try specifying target states directly instead of using the `send()` function. The usage of a dynamic value for ids of the machine and child states seems a bit excessive to me. You could simplify it by using hard-coded values. Could be possible that events are sent to the whole machine instead of sub-states by accident; not sure though. – Tobi Obeck Nov 26 '21 at 17:59

1 Answers1

0

Maybe that happens because dirty is a final state, for example:

{
  id: 'loginForm',
  initial: 'idle',
  context: {
    error: undefined,
  },
  invoke: [
    {
      id: 'usernameInput',
      src: inputMachine,
    },
    {
      id: 'passwordInput',
      src: inputMachine,
    },
    {
      id: 'loginButton',
      src: buttonMachine,
    },
  ],
  states: {
    idle: {
    },
    processing: {

    },
    finished: {
    },
    failed: {
    },
  },
};

I have this machine which spawns some actors, and I can access through loginForm machine in the following way loginForm.state.children.usernamInput, but if one of those machines have reach a final state it returns undefined.

luisbar
  • 676
  • 5
  • 11