@davidkpiano
๐
๐
๐
๐
100%
40%
60%
10%
90%
Delete
Are you sure?
Yes
No
Delete
Undo
Deleted!
๐ฉโ๐ผ
๐โโ๏ธ
๐โโ๏ธ
have one initial state
a finite number of states
a finite number of events
a mapping of state transitionsย
triggered by events
a finite number of final states
Sign in
Signing in...
Profile
Home
SIGN IN
SUCCESS
UNAUTHORIZED
PROFILE
Signed Out
SIGN OUT
Signed Out
Define transitions between
states & events
function searchReducer(state = 'idle', event) {
switch (state) {
case 'idle':
switch (event.type) {
case 'SEARCH':
return 'searching';
default:
return state;
}
case 'searching':
switch (event.type) {
case 'RESOLVE':
return 'success';
case 'REJECT':
return 'failure';
default:
return state;
}
case 'success':
switch (event.type) {
case 'SEARCH':
return 'searching';
default:
return state;
}
case 'failure':
switch (event.type) {
case 'SEARCH':
return 'searching';
default:
return state;
}
default:
return state;
}
}
const machine = {
id: 'feedback',
initial: 'question',
states: {
question: {
on: {
CLICK_GOOD: 'thanks',
CLICK_BAD: 'form',
CLOSE: 'closed',
ESC: 'closed'
}
},
form: {
on: {
SUBMIT: 'thanks',
CLOSE: 'closed',
ESC: 'closed'
}
},
thanks: {
on: {
CLOSE: 'closed',
ESC: 'closed'
}
},
closed: {
type: 'final'
}
}
};
Define transitions between
states & events
const transition = (state, event) => {
return machine
.states[state] // current state
.on[event] // next state
|| state; // or same state
}
import { Machine } from 'xstate';
const feedbackMachine = Machine({
id: 'feedback',
initial: 'question',
states: {
question: {
on: {
CLICK_GOOD: 'thanks',
CLICK_BAD: 'form',
CLOSE: 'closed',
ESC: 'closed'
}
},
form: {
on: {
SUBMIT: 'thanks',
CLOSE: 'closed',
ESC: 'closed'
}
},
thanks: {
on: {
CLOSE: 'closed',
ESC: 'closed'
}
},
closed: {
type: 'final'
}
}
});
npm install xstate
import { feedbackMachine } from './feedbackMachine';
import { useMachine } from '@xstate/react';
const App = () => {
// const [state, dispatch] = useReducer(feedbackReducer, 'question');
const [current, send] = useMachine(feedbackMachine);
if (current.matches('question')) {
return (
<QuestionScreen
onClickGood={() => send({ type: 'GOOD' })}
onClickBad={() => send({ type: 'BAD' })}
onClose={() => send({ type: 'CLOSE' })}
/>
);
} else if (/* ... */) {
// ...
}
}
Idle
Searching...
entry / prefetchResources
entry / fetchResults
Search
[query.length > 0]
H
Idle
Searching...
SEARCH
Success
Failure
RESOLVE
REJECT
SEARCH
Searched
Idle
Searching...
SEARCH
Success
Failure
RESOLVE
REJECT
SEARCH
Searched
const machine = Machine({
initial: 'idle',
states: {
// ...
searching: {
on: {
RESOLVE: 'searched',
REJECT: 'searched.failure'
}
},
searched: {
initial: 'success',
states: {
success: {},
failure: {}
},
on: { SEARCH: 'searching' }
}
}
});
How was your experience?
๐
Good
Bad
Tell me why?
๐
Submit
Thanks for your feedback.
๐
Close
Ain't nothin' but a heartache
Photo by NEW DATA SERVICESย on Unsplash
Success
Signing in
Error
0.9
0.1
Signed out
Signing in
Signed in
SIGN IN
SIGN IN SUCCESS
SIGN IN FAILURE
Sharing...
Shared
Joining...
Joined
share
share Success
Join Success
Join
Leave
End Collab session
Sign in
How was your experience?
๐
Good
Bad
Tell me why?
๐
Submit
Thanks for your feedback.
๐
Close
Give Feedback
โ
โ
Ain't nothin' but a mistake
Login
Gallery
Profile
Camera
SUCCESS
TAP PROFILE
TAP CAMERA
BACK
BACK
Scroll
Login, Gallery, Gallery
Login, Gallery, Camera
Login, Gallery, Profile
0.90, 0.31
0.90, 0.57
0.90, 0.12
0.90
0.31
0.57
0.13
UNAUTHORIZED
0.10
SUCCESS
(decide)
TAP AD
+10
Gallery
Ads interspersed
Ads shown at top
Ad
Scroll
Scroll
TAP AD
+10
-1
-10
INTERSPERSED > .5
TOP > .5
Contextual data
New user?
< 30 days
>= 30 days
Show ad at top
Friend count?
< 100
>= 100
Show more ads
Show less ads
All data
13%
8%
79%
A
B
C
D
E
A
B
C
D
E
import { Machine } from 'xstate';
const myMachine = Machine({
// ...
});
const myService = interpret(myMachine)
.onTransition(state => {
analytics.track({
target: state.value,
source: state.history?.value,
event: sanitize(event),
timestamp: Date.now()
});
})
.start();
Source
Target
Event
A
B
C
D
E
import { Machine } from 'xstate';
import { getShortestPaths } from '@xstate/graph';
const myMachine = Machine({
// ...
});
const shortestPaths = getShortestPaths(myMachine);
A: A
B: A -> B
C: A -> B -> C
D: A -> D
E: A -> D -> E
A
B
C
D
E
import { Machine } from 'xstate';
import { getShortestPaths } from '@xstate/graph';
const myMachine = Machine({
// ...
});
const shortestPaths = getShortestPaths(myMachine);
0.9
1.0
0.4
0.6
0.1
0.4
0.6
A: A
B: A -> D -> E -> B
C: A -> D -> E -> C
D: A -> D
E: A -> D -> E
A
B
C
0.75
0.25
D
E
(D) 0.95
(D) 0.05
A -> B A -> C A -> B A -> B
E -> D -> E -> F ->
-
-
+
+
+
Agent
Environment
Action
State
Reward
service.onTransition(state => {
analytics.track({
source: state.history.value,
target: state.value,
context: state.context,
event: state.event,
timestamp: Date.now()
})
});
Application
Executable Model
Metrics Tracking
Adaptive Transitions
Adaptive UI
xstate
@xstate/react
@xstate/graph
@xstate/vue
@xstate/test
@xstate/analytics
@xstate/sim
@xstate/viz
โ
โ
โ
๐
๐
๐
๐
โ
๐ฎ