@davidkpiano · JSConf Iceland 2018
New APIs
What the different parts are
How these parts are composed together
How things change over time
Dear Microsoft Teams, please fix this ☝️
happy path
// ...
onSearch(query) {
fetch(FLICKR_API + '&tags=' + query)
.then(data => this.setState({ data }));
}
// ...
Show data when results retrieved
// ...
onSearch(query) {
this.setState({ loading: true });
fetch(FLICKR_API + '&tags=' + query)
.then(data => {
this.setState({ data, loading: false });
});
}
// ...
Show loading screen
Show data when results retrieved
Hide loading screen
// ...
onSearch(query) {
this.setState({ loading: true });
fetch(FLICKR_API + '&tags=' + query)
.then(data => {
this.setState({ data, loading: false });
})
.catch(error => {
this.setState({
loading: false,
error: true
});
});
}
// ...
Show loading screen
Show data when results retrieved
Hide loading screen
Show error
Hide loading screen
// ...
onSearch(query) {
this.setState({
loading: true,
error: false
});
fetch(FLICKR_API + '&tags=' + query)
.then(data => {
this.setState({
data,
loading: false,
error: false
});
})
.catch(error => {
this.setState({
loading: false,
error: true
});
});
}
// ...
Show loading screen
Show data when results retrieved
Hide loading screen
Show error
Hide loading screen
Hide error
Hide error
// ...
onSearch(query) {
if (this.state.loading) return;
this.setState({
loading: true,
error: false,
canceled: false
});
fetch(FLICKR_API + '&tags=' + query)
.then(data => {
if (this.state.canceled) {
return;
}
this.setState({
data,
loading: false,
error: false
});
})
.catch(error => {
// fokking fokk
if (this.state.canceled) {
return;
}
this.setState({
loading: false,
error: true
});
});
}
onCancel() {
this.setState({
loading: false,
error: false,
canceled: true
});
}
// ...
Show loading screen
Show data when results retrieved
Hide loading screen
Show error
Hide loading screen
Hide error
Hide error
Search in progress already
Cancel cancellation
Ignore results if cancelled
Ignore error if cancelled
Cancel search
error: false
});
})
.catch(error => {
// fokking fokk
if (this.state.canceled) {
return;
}
this.setState({
loading: false,
error: true
});
});
}
EVENT
State
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
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
Idle
Pending
Rejected
Fulfilled
Fetch
Resolve
reject
Idle
Searching...
SEARCH
Success
Failure
RESOLVE
REJECT
SEARCH
SEARCH
SEARCH
const machine = {
initial: 'idle',
states: {
idle: {
on: { SEARCH: 'searching' }
},
searching: {
on: {
RESOLVE: 'success',
REJECT: 'failure',
SEARCH: 'searching'
}
},
success: {
on: { SEARCH: 'searching' }
},
failure: {
on: { SEARCH: 'searching' }
}
}
};
function transition(state, event) {
return machine.states[state].on[event];
}
Define transitions between
states & actions
Transition function determines
next state from state + event
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
transition(currentState, event) {
const nextState = // ...
Telemetry.sendEvent(
currentState,
nextState,
event
);
return nextState;
}
A
B
C
D
E
A → B
A → B → C
A → D
A → D → E
A
B
C
D
E
A → B
A → B → C
A → D
A → D → E
A → D → E → C
A → D → B → C
A → B → E → C
A → D → E → B → C
A
B
X
Wrong state
E
E
A
B
Unhandled event
E
E
??
A
B
Missing transition
???
C
D
E1
E2
A
B
Missing states
Loading...
E
Success
A
B
E1
C
E2
E2
Race condition
E1
Idle
Searching...
onEntry / prefetchResources
onEntry / fetchResults
Search
[query.length > 0]
H
npm install xstate --save
const lightMachine = Machine({
initial: 'green',
states: {
green: {
on: {
TIMER: 'yellow'
}
},
yellow: {
on: {
TIMER: 'red'
}
},
red: {
on: {
TIMER: 'green'
}
}
}
});
const nextState = lightMachine
.transition('green', 'TIMER');
// State {
// value: 'yellow'
// }
const lightMachine = Machine({
initial: 'green',
states: {
green: {
on: {
TIMER: 'yellow'
}
},
yellow: {
onEntry: ['activateYellow']
on: {
TIMER: 'red'
}
},
red: {
onExit: ['stopCountdown']
on: {
TIMER: 'green'
}
}
}
});
const lightMachine = Machine({
initial: 'green',
states: {
green: {
on: {
TIMER: {
yellow: {
cond: (xs, event) =>
event.elapsed > 10000
}
}
}
},
yellow: {
on: {
TIMER: 'red'
}
},
red: {
on: {
TIMER: 'green'
}
}
}
});
const lightMachine = Machine({
initial: 'green',
states: {
green: {
on: {
TIMER: 'yellow'
}
},
yellow: {
on: {
TIMER: 'red'
}
},
red: {
initial: 'walk',
states: {
walk: {
{ on: { PED_COUNTDOWN: 'wait' } }
},
wait: {
{ on: { PED_COUNTDOWN_END: 'stop' } }
},
stop: {}
}
}
}
});
const lightsMachine = Machine({
parallel: true,
states: {
northSouthLight: {
initial: 'green',
states: {
// ...
}
},
eastWestLight: {
initial: 'red',
states: {
// ...
}
}
}
});
const payMachine = Machine({
initial: 'method',
states: {
method: {
initial: 'card',
states: {
card: {
on: { SELECT_CASH: 'cash' }
},
cash: {
on: { SELECT_CARD: 'card' }
}
},
on: {
NEXT: 'review'
}
},
review: {
on: {
PREV: 'method.$history'
}
}
}
});
Bold ON
Bold OFF
Italics ON
Italics OFF
Underline ON
Underline OFF
Characters
Idle
Searching...
SEARCH
Success
Failure
RESOLVE
REJECT
SEARCH
SEARCH
SEARCH
Idle
Searching...
SEARCH
Success
Failure
RESOLVE
REJECT
SEARCH
SEARCH
Searched
Statecharts
FSMs
Bottom-up
States & logic
Code Complexity
Write once, write anywhere
Learn once, write anywhere
Model once, implement anywhere
@davidkpiano · JSConf Iceland 2018