Fetch data
π
GET api/users
Pending
200 VALE
Fetch data
π
GET api/users
Cargando...
200 VALE
Fetch data
π
GET api/users
Cargando
π
GET api/users
Cargando
π
GET api/users
Cargando
π
GET api/users
Cargando
π
GET api/users
QuΓ© narices
Fetch data
π
GET api/users
Cargando
200 VALE
Fetch data
π
GET api/users
Cargando
π
GET api/users
Cargando
π
GET api/users
Cargando
π
GET api/users
Cargando
Fetch data
Everyone is
different.
A/B tests
are ineffective.
User flows are
seldom linear.
π
π
π
π
Adaptive user interfaces (AUI)
Adapt to people,
not just devices
Delete
Are you sure?
Yes
No
Delete
Undo
Deleted!
Developing AUIs
is challenging.
Model-driven
development
Finite state machines
-
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
SUBMIT
SUCCESS
UNAUTHORIZED
PROFILE
Statecharts
(Hierarchical State Machines)
Statecharts
Idle
Searching...
entry / prefetchResources
entry / fetchResults
Search
- ActionsΒ - onEntry, onExit, transition
- GuardsΒ - conditional transitions
[query.length > 0]
Statecharts
- ActionsΒ - onEntry, onExit, transition
- GuardsΒ - conditional transitions
- HierarchyΒ - nested states
- OrthogonalityΒ - parallel states
- HistoryΒ - remembered states
H
Idle
Searching...
SEARCH
Success
Failure
RESOLVE
REJECT
SEARCH
Searched
Hierarchical states
Idle
Searching...
SEARCH
Success
Failure
RESOLVE
REJECT
SEARCH
Searched
Hierarchical states
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 us why?
π
Submit
Thanks for your feedback.
π
Close
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;
}
}
FSMs with switch/case
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
}
FSMs with object mapping
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 (/* ... */) {
// ...
}
}
- Abstract model
- Transition analytics
- Identify adaptive paths
- Use analysis for adaptation
Game plan
Photo by NEW DATA SERVICESΒ on Unsplash
Success
Signing in
Error
0.9
0.1
Weighted graphs
How was your experience?
π
Good
Bad
Tell us why?
π
Submit
Thanks for your feedback.
π
Close
Give Feedback
β
β
Login
Gallery
Profile
Camera
SUCCESS
TAP PROFILE
TAP CAMERA
BACK
BACK
Scroll
Analysis of transitions
πΊ 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
Analysis of actions
+10
Gallery
Ads interspersed
Ads shown at top
Ad
Scroll
Scroll
TAP AD
+10
-1
-10
INTERSPERSED > .5
TOP > .5
Decision trees
Contextual data
- Registration date
- Number of friends
- Posting frequency
- Location
- ...etc.
New user?
< 30 days
>= 30 days
Show ad at top
Friend count?
< 100
>= 100
Show more ads
Show less ads
All data
Prediction via
shortest paths
A
B
C
D
E
- Shortest pathΒ algorithms (Dijkstra, Bellman-Ford, A* search, etc.)
- AnalyticsΒ provides weights
- Represents happy paths
- Can be automatically generated
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
? state.history.value
: undefined,
event: sanitize(event),
timestamp: Date.now()
});
})
.start();
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 ->
Higher-order Markov models
Agent
Environment
Action
State
Reward
Reinforcement
learning
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
Determinism
Visualization
π©βπ» Developer Experience
Communication
Analytics
Simulation
Testability
π¨βπ©βπ§βπ¦ User Experience
Prevent Information Overload
Provide intuitive guidance
Understand Tasks & Goals
Create Personalized Systems
Adaptive User Interfaces
Micro vs. Macro Adaptivity
Prediction vs. Feedback
Local vs. Global
Data vs. Simulation
Modeling considerations
The future of state
is nothing new.
Advantages
of using statecharts
- Visualized modeling
- Precise diβagrams
- Automatic code generation
- Comprehensive test coverage
- Accommodation of late-breaking requirements changes
Disadvantages
of using statecharts
Learning curve
Modeling requires planning ahead
Not everything can be modeled (yet)
Statecharts
FSMs
Bottom-up
Complexity
trade-offs
States & logic
Code Complexity
The world of statecharts
Spectrum community
XState Docs
xstate
@xstate/react
@xstate/graph
@xstate/vue
@xstate/test
@xstate/analytics
@xstate/sim
@xstate/viz
β
β
β
π
π
π
π
β
Make your code do more.
Thank you React Alicante πͺπΈ
Mind Reading with Intelligent & Adaptive UIs
By David Khourshid
Mind Reading with Intelligent & Adaptive UIs
React Alicante 2019
- 3,634