XState React - Invoke Service not triggered

1.3k views Asked by At

I am using xstate with react to implement a basic login functionality. The code is here and the issue I am facing is, on the event AUTHENTICATING it is meant to invoke a service authenticateUser and it is not invoking. No visible errors in the console. The component looks like

import { useMachine } from "@xstate/react";
import { createMachine, assign } from "xstate";
import "./App.css";

const authenticateUserNew = async (c, e) => {
  console.log("service invoked");
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.5) {
        resolve();
      } else {
        reject();
      }
    }, 1000);
  });
};

const loginMachine = createMachine(
  {
    id: "login-machine",
    initial: "unauthenticated",
    context: {
      isAuthenticated: false,
    },
    states: {
      unauthenticated: {
        on: {
          AUTHENTICATING: {
            invoke: {
              id: "authenticateUser",
              src: (c, e) => authenticateUserNew(c, e),
              onDone: {
                target: "authenticated",
                actions: assign({ isAuthenticated: (context, event) => true }),
              },
              onError: {},
            },
          },
        },
      },
      authenticated: {
        on: {
          LOGOUT: {
            target: "unauthenticated",
          },
        },
      },
    },
  },
  {
    services: {
      authenticateUser: () => {
        console.log("service invoked");
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            if (Math.random() > 0.5) {
              resolve();
            } else {
              reject();
            }
          }, 1000);
        });
      },
    },
  }
);

function App() {
  const [current, send] = useMachine(loginMachine);
  return (
    <div className="App">
      <h2>{current.value}</h2>
      <br />
      <h3>
        isAuthenticated: {current.context.isAuthenticated ? "True" : "False"}
      </h3>
      <br />
      <button onClick={() => send("AUTHENTICATING")}>AUTHENTICATE</button>
      <br />
      <button onClick={() => send("LOGOUT")}>LOGOUT</button>
    </div>
  );
}

export default App;

I have tried both the approach where I can externalize function and use it or define it inside the service section of state machine, in both the case it wasn't invoked.

1st approach

invoke: {
  id: "authenticateUser",
  src: (c, e) => authenticateUserNew(c, e),
  onDone: {
    target: "authenticated",
    actions: assign({ isAuthenticated: (context, event) => true }),
  },
  onError: {},
}

2nd approach

invoke: {
  id: "authenticateUser",
  src: "authenticateUser",
  onDone: {
    target: "authenticated",
    actions: assign({ isAuthenticated: (context, event) => true }),
  },
  onError: {},
}

React version: ^17.0.2 xstate: ^4.3.5 @xstate/react: 2.0.1

1

There are 1 answers

1
Giovanni Londero On BEST ANSWER

From docs:

An invocation is defined in a state node's configuration with the invoke property

You are instead trying to invoke in an event node, not a state one.

For example, you could do:

...
    states: {
      unauthenticated: {
        on: {
          AUTHENTICATE: {
            target: 'authenticating'
          },
        },
      },
      authenticating: {
        invoke: {
          id: "authenticateUser",
          src: 'authenticateUser',
          onDone: {
            target: "authenticated",
            actions: assign({ isAuthenticated: (context, event) => true }),
          },
          onError: {
            target: 'unauthenticated'
          },
        },
      },
      authenticated: {
        on: {
          LOGOUT: {
            target: "unauthenticated",
          },
        },
      },
    },
...

and send the AUTHENTICATE event:

<button onClick={() => send("AUTHENTICATE")}>AUTHENTICATE</button>

Moreover, I'd like to suggest to avoid the isAuthenticated at all. You can check if you're authenticated with the matches method:

<h3>
    isAuthenticated: {current.matches('authenticated') ? "True" : "False"}
</h3>