1

I have xstate react script whereby a user fills in a form, and presses submit. On submit the xstate received a send("VALIDATE", {formData}) and that is run through a service that validates the form. On success the script transitions to the target: "success" and i need that final "success" state to call an external function which actually does the saving of the script.

I can get the data into the validator function, BUT, after the onDone, the subsequent success state doesn't appear to see the data.

How can I wire the data from the validating event to the success event??

 id: 'validator',
        initial: 'populating',
        context: {},
        states: {
        populating: {
            on: {
            VALIDATE: 'validating'
            }
        },
        validating: {
            invoke: {
            src: (context, data) => doValidate(data),
            onDone: {
                target: 'success',
                actions: assign({ data: "hello world"})
            },
            onError: 'failure'
            }
        },
        success: {
            invoke: {
                // I do see the hello world here, but what I want is the 'data' from the  doValidate(data)
                src: (ctx)=>{console.log("invoked success, what can I see here: ", ctx)}
            } 
        },

I'm triggering the validate via: send("VALIDATE", formData)

Nathan Leggatt
  • 398
  • 1
  • 6
  • 18

2 Answers2

2

If I understood you correctly you want the event from the first service onDoneto be available to the second service? The easiest way would be to put the data on the context and access it in the other state/service and delete it afterward.

Or you can model your machine to send custom event to itself when the first service is done.

import { createMachine, assign, send, interpret } from "xstate";

const machine = createMachine({
  preserveActionOrder: true, //make sure actions are executed in order
  id: "validator",
  initial: "populating",
  context: {},
  states: {
    populating: {
      on: {
        VALIDATE: "validating"
      }
    },
    validating: {
      on: {
        // Listen to the event that fires when the first service is done
        FIRST_SERVICE_DONE: {
          target: "success"
        }
      },
      invoke: {
        src: (context, data) => Promise.resolve({ prop: "first service" }),
        onDone: {
          // target: 'success',
          actions: [
            assign({ data: "hello world" }),
            //instead of using target:'success' send a custom 
            //event that has access to the data from the service
            send((context, event) => {
              //event here has  event.data.prop === 'first service'
              console.log("send evt ", event);
              return {
                type: "FIRST_SERVICE_DONE",
                prop: event.data.prop
              };
            })
          ]
        },
        onError: "failure"
      }
    },
    success: {
      invoke: {
        src: (_ctx, evt) => {
          console.log("evt ", evt.prop);  //first service
        }
      }
    },
    failure: {}
  }
});

const service = interpret(machine);

service.start();

service.send({ type: "VALIDATE" });

Codesandbox

Ivan V.
  • 7,593
  • 2
  • 36
  • 53
1

In Xstate the context is the extended state, so it doesn't seem like a good practice to use the context as a "machine memory". The extended state is used so that you don't have a potentially infinite number of states.

In case you need to preserve information that is sent by the event going to the state that invokes a Promise, you can add that information to the response. For example:

export const myMachineServices = {
  myRequest: (context, event) =>
    new Promise(resolve => {
      setTimeout(() => {
        resolve({
          payload: 'some data',
        })
      }, 1000)
    }).then(res => ({
      ...res,
      event.data
    }))
    .catch(err => ({
      ...err,
      event.data
    })),
}