@davidkpiano Β· Finch Front-End 2019
Fetch data
Fetch data
Fetch data
Fetch data
Normal
Hover
Active
Disabled
Fetched data
Success
Failed to fetch
Error
Fetching data...
Loading
Fetch data
GET api/users
Pending
200 OK
Fetch data
GET api/users
Pending
200 OK
Fetch data
GET api/users
Pending
GET api/users
Pending
GET api/users
Pending
GET api/users
Pending
GET api/users
Pending
Fetch data
GET api/users
Pending
200 OK
Fetch data
GET api/users
Pending
GET api/users
Pending
GET api/users
Pending
GET api/users
Pending
Fetch data
I'm still quite rubbish
input[type="checkbox"] {
/* ... */
}
input[type="checkbox"] ~ #app {
/* ... */
}
input[type="checkbox"] ~ #app .box {
background-color: white;
}
input[type="checkbox"]:checked
~ #app .box {
background-color: blue;
}
checkbox
#app
.box
Adjacent sibling selector
label for="..."
label for="..."
checkboxes
radios
Zero or more
One at a time
Fetching data...
<button class="loading">
Fetching data...
</button>
<button class="success">
Fetched data!
</button>
<button class="loading success">
Fetched data...?
</button>
Fetched data!
Fetched data...?
User flows are transitions
between UI states
due to events
A state is a dynamic property expressing characteristics of an object that may change in response to user action or automated processes. States do not affect the essential nature of the object, but represent data associated with the object or user interaction possibilities.Β
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
Fulfilled
Rejected
Idle
Searching...
SEARCH
Success
Failure
RESOLVE
REJECT
SEARCH
SEARCH
SEARCH
π«
SEARCH?
<button data-state="loading">
Fetching data...
</button>
button[data-state="loading"] {
opacity: 0.5;
}
elButton.dataset.state; // 'loading'
elButton.dataset.state = 'success';
delete elButton.dataset.state;
HTML
CSS
JavaScript
<button data-state="success">
Fetched data!
</button>
<button>
Fetch data
</button>
Fetching data...
<button data-state="idle">
Fetch data
</button>
<button data-state="loading">
Fetching data...
</button>
<button data-state="success">
Fetched data!
</button>
<button data-state="error">
Failed to fetch data
</button>
Fetched data!
Failed to fetch
Fetch data
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 = {
initial: 'idle',
states: {
idle: {
on: { SEARCH: 'searching' }
},
searching: {
on: {
RESOLVE: 'success',
REJECT: 'failure',
SEARCH: 'searching'
}
},
success: {
on: { SEARCH: 'searching' }
},
failure: {
on: { SEARCH: 'searching' }
}
}
};
Define transitions between
states & events
const transition = (state, event) => {
return machine
.states[state] // current state
.on[event] // next state
|| state; // or same state
}
NAV2
NAV1
START
panup
PANDOWN
panup
PANDOWN
npm install xstate
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' }
}
}
};
import { Machine } from 'xstate';
const machine = Machine({
initial: 'idle',
states: {
idle: {
on: { SEARCH: 'searching' }
},
searching: {
on: {
RESOLVE: 'success',
REJECT: 'failure',
SEARCH: 'searching'
}
},
success: {
on: { SEARCH: 'searching' }
},
failure: {
on: { SEARCH: 'searching' }
}
}
});
import { Machine } from 'xstate';
const machine = Machine({
// ...
});
const searchingState = machine
.transition('idle', 'searching');
searchingState.value;
// => 'searching'
import { Machine, interpret } from 'xstate';
const machine = Machine({
// ...
});
const service = interpret(machine)
.onTransition(state => {
console.log(state);
})
.start();
service.send('SEARCH');
// State {
// value: 'searching',
// ...
// }
[data-state="idle"]
[data-show="loading"]
[data-hide="loading"]
data-active
data-active
activeΒ when app is in "loading" state
inactiveΒ when app is in "loading" state
[data-state="loading"]
const elApp = document.querySelector('#app');
function setAppState(state) {
// change data-state attribute
elApp.dataset.state = state;
// remove any active data-attributes
document.querySelectorAll(`[data-active]`)
.forEach(el => {
delete el.dataset.active;
});
// add active data-attributes to proper elements
document
.querySelectorAll([
`[data-show~="${state}"]`,
`[data-hide]:not([data-hide~="${state}"])`
].join(','))
.forEach(el => {
el.dataset.active = true;
});
}
// set button state to 'loading'
setAppState('loading');
[data-show~="${state}"]
[data-hide]:not([data-hide~="${state}"])
<main data-state="loading" data-prev-state="idle">
idle
loading
error
<main data-state="loading" data-prev-state="error">
const elApp = document.querySelector('#app');
function setAppState(state) {
// change data-state attribute
elApp.dataset.prevState = elApp.dataset.state;
elApp.dataset.state = state;
// ...
}
β¨β¨β¨
π₯π₯π₯
[data-state="loading"][data-prev-state="idle"] {
/* loading initially */
}
[data-state="loading"][data-prev-state="error"] {
/* loading after error */
}
start
selecting
selected
dragging
disposed
grabbing
Idle
Searching...
entry / prefetchResources
entry / fetchResults
Search
[query.length > 0]
H
Idle
Searching...
SEARCH
Success
Failure
RESOLVE
REJECT
SEARCH
SEARCH
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' }
}
}
});
#app[data-state] {
/* has attribute */
}
#app[data-state="idle"] {
/* attribute equals exactly */
}
#app[data-state*="err"] {
/* attribute contains */
}
#app[data-state^="idle"] {
/* attribute starts with */
}
#app[data-state$="error"] {
/* attribute ends with */
}
#app[data-state~="idle.error"] {
/* attribute contains (spaced) */
}
<main id="app" data-state="idle">
<!-- ... -->
</main>
<main id="app" data-state="idle idle.error">
<!-- ... -->
</main>
Statecharts
FSMs
Bottom-up
States & logic
Code Complexity
@davidkpiano Β· Finch Front-End 2019