Infinitely Better UIs
with
David Khourshid - Microsoft
@davidkpiano
Finite Automata
Developing user interfaces
is not easy


Deterministic Finite Automata
(Finite State Machines)




-
Combinatorial logic
-
Finite state machines
-
Pushdown automata
-
Turing machines
-
Configuring Webpack
Automata theory
React makes us
rethink
class GoatButton extends Component {
state = {
goat: undefined
};
fetchGoat = () => {
fetch('url/to/goat')
.then(goat => this.setState({ goat }));
}
render() {
const { goat } = this.state;
return (
<div>
{goat ? <img src={goat} /> : null}
<button onClick={this.fetchGoat}>
Fetch Goat
</button>
</div>
);
}
}
- Goat search fails?
- Button clicked repeatedly?
- Disable button
- Loading message
- Error message
Set goat in state
Handle fetching the goat
Render goat button and image
state = {
goat: undefined,
goatError: false,
goatSearching: false
};
Add boolean flags
fetchGoat = () => {
if (this.state.goatSearching) return;
this.setState({
goatSearching: true,
goatError: false
});
fetch('url/to/goat')
.then(goat => this.setState({
goat,
goatSearching: false
}))
.catch(err => this.setState({ goatError: true });
}
Don't search if already searching
Indicate an error
render() {
const { goat, goatError, goatSearching } = this.state;
return (
<div>
{goat && <img src={goat} />}
{goatError && <span>Error: goat not found</span>}
<button onClick={this.fetchGoat} disabled={goatSearching}>
{goatSearching
? 'Searching...'
: goatError
? 'Goat fail, retry?'
: 'Fetch goat'
}
</button>
</div>
);
}
Disable button if loading
Custom button text
class GoatButton extends Component {
state = {
goat: undefined
};
fetchGoat = () => {
fetch('url/to/goat')
.then(goat => this.setState({ goat }));
}
render() {
const { goat } = this.state;
return (
<div>
{goat ? <img src={goat} /> : null}
<button onClick={this.fetchGoat}>
Fetch Goat
</button>
</div>
);
}
}
class GoatButton extends Component {
state = {
goat: undefined,
goatError: false,
goatSearching: false
};
fetchGoat = () => {
if (this.state.goatSearching) return;
this.setState({
goat: undefined,
goatSearching: true,
goatError: false
});
fetch('url/to/goat')
.then(goat => this.setState({ goat, goatSearching: false }))
.catch(err => this.setState({ goatError: true });
}
render() {
const { goat, goatError, goatSearching } = this.state;
return (
<div>
{goat && <img src={goat} />}
{goatError && <span>Error: goat not found</span>}
<button onClick={this.fetchGoat} disabled={goatSearching}>
{goatSearching
? 'Searching...'
: goatError
? 'Goat fail, retry?'
: 'Fetch goat'
}
</button>
</div>
);
}
}
Before
Aw π©
Bottom-up
ππ
approach
Event
Action C
Action A
Action E
Action B
Action D
Business logic
ππ
Code
Difficult to understand
Difficult to test
Will contain bugs
Difficult to enhance
Features make it worse
Source: Ian Horrocks, "Constructing the User Interface with Statecharts", ch. 3 pg. 17
-
props
-
setState()
-
props
-
setState()
-
callback props
-
context
-
Flux
-
Redux
-
MobX
-
RxJS
-
monads?
User interfaces are
(Trees are graphs, too)
graphs
Deterministic
Finite
Automata
Deterministic
Finite
Automata
Number of possible states
Deterministic
Finite
Automata
Predetermined sequence
Deterministic
Finite
Automata
State + action = next state, always
Deterministic
Finite
Automata
State Machines
Designing
state machines
It's time for some
game
graph theory
PILL
TIMER
EAT
REVIVE
An initial state
A finite number of states
Transitions between states
Actions that cause transitions
idle
loading
goat
error
CLICK
RESOLVE
REJECT
CLICK
CLICK
State Transition Diagram
STDΒ just call it this π
State machines provide a
common language
for designers & developers.
Developing
state machines

-
idle -> loading
-
loading -> goat -> error
-
error -> loading
-
goat -> loading
Adjacency list

const machine = {
idle: {
CLICK: 'loading'
},
loading: {
RESOLVE: 'goat',
REJECT: 'error',
},
goat: {
CLICK: 'loading'
},
error: {
CLICK: 'loading'
}
};
const initialState = 'idle';
const machine = {
// ...
};
const initialState = 'idle';
function transition(state, action) {
return machine[state][action];
}
const t=(m,s,a)=>m[s][a]
state = {
goatState: initialState, // 'idle'
goat: undefined
};
commands = {
loading: this.fetchGoat
}

Finite state
Data
Commands (side effects)
transition = (action) => {
const { goatState } = this.state;
const nextState = machine[goatState][action];
const command = this.commands[nextState];
this.setState({
goatState: nextState
}, command);
}


Ryan
state
Transition function!
Next command
Set state, then exec command
fetchGoat = () => {
fetch('url/to/goat')
.then(goat => this.setState({ goat },
() => this.transition('RESOLVE')
))
.catch(_ => this.transition('REJECT'));
}

render() {
const { goat, goatState } = this.state;
const buttonText = {
idle: 'Fetch goat'
loading: 'Loading...',
error: 'Goat fail, retry?',
goat: 'Fetch another goat'
}[goatState];
return (
<div>
{goat && <img src={goat} />}
{goatState === 'error' && <span>Error: goat not found</span>}
<button
onClick={() => this.transition('CLICK')}
disabled={goatState === 'loading'}>
{buttonText}
</button>
</div>
);
}

Declarative rendering
Testing & Visualizing
state machines
oh god conference wifi
with FSMs
Trade-offsΒ



π₯ State explosion π₯
Statecharts
Hierarchical finite state machines

Hierarchical states


Concurrent states
History states

XState
Announcing
npm install xstate
import { Machine } from xstate;
const machine = Machine({ ... });
const nextState = machine
.transition(currentState, action);
states:
green
on:
yellow
red
states:
TIMER
yellow
on:
TIMER
red
walk ...
wait ...
stop ...
on:
TIMER
green
XState
Announcing
npm install xstate
lightMachine
.transition('red.stop', 'TIMER')
.toString();
// 'green'
lightMachine
.transition('yellow', 'TIMER')
.value;
// { red: 'walk' }
Hierarchical states
XState
Announcing
npm install xstate
textMachine
.transition('bold.off', 'TOGGLE_BOLD')
.value;
// {
// bold: 'on',
// italics: 'off',
// underline: 'off',
// list: 'bullets'
// }
Concurrent states

- Precise semantics
- Rich, expressive notation
- Easily understandable
- Fast to create
- Easy to achieve complete UI specs
Resources
Advantages

Thank you,Β
React Rally!
David Khourshid
@davidkpiano
Infinitely Better UIs with Finite Automata
By David Khourshid
Infinitely Better UIs with Finite Automata
- 35,788