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
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>