David Khourshid → @davidkpiano
stately.ai
function Conference() {
const [startDate, setStartDate] = useState(new Date());
const [endDate, setEndDate] = useState(new Date());
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [location, setLocation] = useState('');
const [url, setUrl] = useState('');
const [image, setImage] = useState('');
const [price, setPrice] = useState(0);
const [attendees, setAttendees] = useState(0);
const [organizer, setOrganizer] = useState('');
const [countries, setCountries] = useState([]);
const [categories, setCategories] = useState([]);
const [tags, setTags] = useState([]);
const [swag, setSwag] = useState([]);
const [speakers, setSpeakers] = useState([]);
const [sponsors, setSponsors] = useState([]);
const [videos, setVideos] = useState([]);
const [tickets, setTickets] = useState([]);
const [schedule, setSchedule] = useState([]);
const [socials, setSocials] = useState([]);
const [coffee, setCoffee] = useState([]);
const [codeOfConduct, setCodeOfConduct] = useState('');
// ...
}
const [count, setCount] = useState(0);
const counter = document.querySelector('#counter');
const incButton = document.querySelector('#inc');
const decButton = document.querySelector('#dec');
let count = 0;
incButton.addEventListener('click', () => {
count++;
counter.textContent = count.toString();
}
decButton.addEventListener('click', () => {
count--;
counter.textContent = count.toString();
}
const counter = document.querySelector('#counter');
const incButton = document.querySelector('#inc');
const decButton = document.querySelector('#dec');
let count = 0;
incButton.addEventListener('click', () => {
if (count < 10) {
count++;
counter.textContent = count.toString();
}
});
decButton.addEventListener('click', () => {
if (count > 0) {
count--;
counter.textContent = count.toString();
}
});
const counter = document.querySelector('#counter');
const incButton = document.querySelector('#inc');
const decButton = document.querySelector('#dec');
let count = 0;
incButton.addEventListener('click', () => {
if (count < 10) {
count++;
counter.textContent = count.toString();
}
});
decButton.addEventListener('click', () => {
if (count > 0) {
count--;
counter.textContent = count.toString();
}
});
counter.addEventListener('keyup', (event) => {
if (event.key === 'ArrowUp' && count < 10) {
count++;
counter.textContent = count.toString();
} else if (event.key === 'ArrowDown' && count > 0) {
count--;
counter.textContent = count.toString();
}
});
counter.addEventListener('keydown', (event) => {
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
event.preventDefault();
}
});
const counter = document.querySelector('#counter');
const incButton = document.querySelector('#inc');
const decButton = document.querySelector('#dec');
let count = 0;
function incrementCount() {
if (count < 10) {
count++;
counter.textContent = count.toString();
}
}
function decrementCount() {
if (count > 0) {
count--;
counter.textContent = count.toString();
}
}
incButton.addEventListener('click', incrementCount);
decButton.addEventListener('click', decrementCount);
counter.addEventListener('keyup', (event) => {
if (event.key === 'ArrowUp') {
incrementCount();
} else if (event.key === 'ArrowDown') {
decrementCount();
}
});
counter.addEventListener('keydown', (event) => {
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
event.preventDefault();
}
});
const counter = document.querySelector('#counter');
const incButton = document.querySelector('#inc');
const decButton = document.querySelector('#dec');
const output = document.querySelector('#output');
function createObservable(initialValue) {
let value = initialValue;
const listeners = new Set();
return {
get() {
return value;
},
set(newValue) {
value = newValue;
listeners.forEach(listener => listener(value));
},
subscribe(listener) {
listeners.add(listener);
return { unsubscribe: () => listeners.delete(listener) }
},
};
}
const countObservable = createObservable(0);
incButton.addEventListener('click', () => {
if (count < 10) {
countObservable.set(count + 1);
}
});
decButton.addEventListener('click', () => {
if (count > 0) {
countObservable.set(count - 1);
}
});
counter.addEventListener('keyup', (event) => {
if (event.key === 'ArrowUp') {
countObservable.set(count + 1);
} else if (event.key === 'ArrowDown') {
countObservable.set(count - 1);
}
});
counter.addEventListener('keydown', (event) => {
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
event.preventDefault();
}
});
countObservable.subscribe(count => {
counter.textContent = count;
output.textContent = count;
});
const counter = document.querySelector('#counter');
const incButton = document.querySelector('#inc');
const decButton = document.querySelector('#dec');
const output = document.querySelector('#output');
function createObservable(transitionFn, initialValue) {
let value = initialValue;
const listeners = new Set();
return {
get() {
return value;
},
send(event) {
value = transitionFn(value, event);
listeners.forEach((listener) => listener(value));
},
subscribe(listener) {
listeners.add(listener);
return { unsubscribe: () => listeners.delete(listener) }
},
};
}
const countObservable = createObservable((count, event) => {
switch (event.type) {
case 'inc':
return Math.min(10, count + 1);
case 'dec':
return Math.max(0, count - 1);
default:
return count;
}
}, 0);
incButton.addEventListener('click', () => {
countObservable.send({ type: 'inc' });
});
decButton.addEventListener('click', () => {
countObservable.send({ type: 'dec' });
});
counter.addEventListener('keyup', (event) => {
if (event.key === 'ArrowUp') {
countObservable.send({ type: 'inc' });
} else if (event.key === 'ArrowDown') {
countObservable.send({ type: 'dec' });
}
});
counter.addEventListener('keydown', (event) => {
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
event.preventDefault();
}
});
countObservable.subscribe((count) => {
counter.textContent = count;
output.textContent = count;
});
You just
reinvented
🎉
I don't need a
state management library.
I don't need a
state management library.
I should have used a
state management library.
let count = 0;
count++;
const count = 0;
const nextCount = count + 1;
(state, event) => (nextState, )
effects
(state, event) => nextState
switch (state) {
case 'mini':
if (event.type === 'toggle') {
playVideo();
return 'full';
}
break;
case 'full':
if (event.type === 'toggle') {
pauseVideo();
return 'mini';
}
break;
default:
break;
}
👩💻
🧔
👩🍳
☕️❔
📝
💶❓
💶
📄✅
☕️
☕️
👩💻
🧔
I would like a coffee...
What would you like?
Actor
I would like a coffee, please.
Actor
💭
💬
💬
Here you go. ☕️
Thanks!
someActor.send({
type: 'greet',
value: 'Buenos',
from: self
});
// ...
switch (event.type) {
case 'greet':
message.from.send({
type: 'greet',
value: 'Que tal',
from: self
});
// ...
}
{
orders: [/* ... */],
inventory: {
// ...
},
sales: 134.65
}
send(event)
Event-based
setState(value)
Value-based
// Read state
actor.subscribe((state) => {
console.log(state);
});
// Update state (indirectly)
actor.send({ type: 'inc' });
Given a user is logged out,
When the user logs in with correct credentials
Then the user should be logged in
function transition(state, event) {
switch (state.value) {
case 'cart':
if (event.type === 'CHECKOUT') {
return { value: 'shipping' };
}
return state;
case 'shipping':
// ...
default:
return state;
}
}
State
Event
const machine = {
initial: 'cart',
states: {
cart: {
on: {
CHECKOUT: 'shipping'
}
},
shipping: {
on: {
NEXT: 'contact'
}
},
contact: {
// ...
},
// ...
}
}
function transition(state, event) {
const nextState = machine
.states[state]
.on?.[event.type]
?? state;
}
transition('cart', { type: 'CHECKOUT' });
// => 'shipping'
transition('cart', { type: 'UNKNOWN' });
// => 'cart'
import { createMachine } from "xstate";
export const machine = createMachine({
initial: 'cart',
states: {
cart: {
on: {
CHECKOUT: { target: 'shipping' }
}
},
shipping: {
on: {
NEXT: { target: 'contact' }
}
},
contact: {
// ...
},
// ...
}
})
import { createActor } from 'xstate';
import { machine } from './ghostMachine';
const actor = createActor(machine);
actor.subscribe(state => {
console.log(state);
});
actor.start();
// { value: 'checkout', ... }
actor.send({ type: 'CHECKOUT' });
// { value: 'shipping', ... }
cart
shipping
contact
payment
confirmation
CHECKOUT
NEXT
NEXT
ORDER
PAYPAL
BACK
BACK
CANCEL
cart
shipping
contact
payment
confirmation
CHECKOUT
NEXT
NEXT
ORDER
PAYPAL
As a user, when I'm in the cart and I click the checkout button, I should be on the shipping page.
cart
shipping
contact
payment
confirmation
CHECKOUT
NEXT
NEXT
ORDER
PAYPAL
As a user, when I'm in the cart and I checkout via PayPal, I should be taken directly to the payment screen.
cart
shipping
contact
payment
confirmation
CHECKOUT
NEXT
NEXT
ORDER
PAYPAL
confirmation state
cart
shipping
contact
payment
confirmation
CHECKOUT
NEXT
NEXT
ORDER
PAYPAL
confirmation state where
shipping address is provided
Bug-free
Intuitive UX
Accessible
No race conditions
Adheres to specifications
Verifiable logic
Adding features
Changing features
Removing features
Fixing bugs
Adjusting tests
Onboarding
Documentation
Understanding bug root causes
Performance
Testability
Stability at complexity scale
1
2
3
4
David Khourshid → @davidkpiano
stately.ai