|
| 1 | +--- |
| 2 | +title: Core Concepts |
| 3 | +tags: core |
| 4 | +permalink: core/concepts.html |
| 5 | +--- |
| 6 | + |
| 7 | +Robot is built around finite state machines. Understanding these core concepts will help you build more predictable and maintainable applications. |
| 8 | + |
| 9 | +## STATE |
| 10 | + |
| 11 | +<span id="state"></span> |
| 12 | + |
| 13 | +States are the foundation of any state machine. At any given time, your machine is in exactly one state. States represent the different modes or conditions your application can be in. |
| 14 | + |
| 15 | +```js |
| 16 | +import { createMachine, state } from 'robot3'; |
| 17 | + |
| 18 | +const machine = createMachine({ |
| 19 | + idle: state(), |
| 20 | + loading: state(), |
| 21 | + loaded: state(), |
| 22 | + error: state() |
| 23 | +}); |
| 24 | +``` |
| 25 | + |
| 26 | +States in Robot are declarative - you define what states exist and what transitions are possible from each state. This makes your application's behavior predictable and easy to reason about. |
| 27 | + |
| 28 | +### Initial State |
| 29 | + |
| 30 | +Every machine starts in the first state defined. In the example above, the machine begins in the `idle` state. |
| 31 | + |
| 32 | +### Final States |
| 33 | + |
| 34 | +States with no transitions are considered final states. When a machine reaches a final state, it stays there unless externally reset. |
| 35 | + |
| 36 | +## TRANSITIONS |
| 37 | + |
| 38 | +<span id="transitions"></span> |
| 39 | + |
| 40 | +Transitions define how your machine moves from one state to another in response to events. They're the arrows in your state diagram. |
| 41 | + |
| 42 | +```js |
| 43 | +import { createMachine, state, transition } from 'robot3'; |
| 44 | + |
| 45 | +const machine = createMachine({ |
| 46 | + idle: state( |
| 47 | + transition('fetch', 'loading') |
| 48 | + ), |
| 49 | + loading: state( |
| 50 | + transition('done', 'loaded'), |
| 51 | + transition('error', 'error') |
| 52 | + ), |
| 53 | + loaded: state(), |
| 54 | + error: state( |
| 55 | + transition('retry', 'loading') |
| 56 | + ) |
| 57 | +}); |
| 58 | +``` |
| 59 | + |
| 60 | +### Transition Syntax |
| 61 | + |
| 62 | +Each transition takes: |
| 63 | +- An event name (what triggers the transition) |
| 64 | +- A target state (where to go when triggered) |
| 65 | +- Optional guards and actions |
| 66 | + |
| 67 | +Transitions are deterministic - for any given state and event combination, there's only one possible outcome. |
| 68 | + |
| 69 | +## EVENTS |
| 70 | + |
| 71 | +<span id="events"></span> |
| 72 | + |
| 73 | +Events are the triggers that cause state transitions. They represent things that happen in your application - user interactions, API responses, timers, etc. |
| 74 | + |
| 75 | +```js |
| 76 | +import { interpret } from 'robot3'; |
| 77 | + |
| 78 | +const service = interpret(machine, () => { |
| 79 | + console.log('State changed to:', service.machine.current); |
| 80 | +}); |
| 81 | + |
| 82 | +// Send events to trigger transitions |
| 83 | +service.send('fetch'); |
| 84 | +service.send('done'); |
| 85 | +``` |
| 86 | + |
| 87 | +### Event Data |
| 88 | + |
| 89 | +Events can carry data that's used by guards and actions: |
| 90 | + |
| 91 | +```js |
| 92 | +service.send({ type: 'done', data: { user: 'Alice' } }); |
| 93 | +``` |
| 94 | + |
| 95 | +### Event Types |
| 96 | + |
| 97 | +Robot supports several types of events: |
| 98 | +- **User events**: Explicitly sent via `service.send()` |
| 99 | +- **Immediate transitions**: Automatically triggered when entering a state |
| 100 | +- **Invoked promises**: Success/error events from async operations |
| 101 | + |
| 102 | +## GUARDS |
| 103 | + |
| 104 | +<span id="guards"></span> |
| 105 | + |
| 106 | +Guards are conditions that must be met for a transition to occur. They let you add conditional logic to your state machines without complicating the state structure. |
| 107 | + |
| 108 | +```js |
| 109 | +import { createMachine, state, transition, guard } from 'robot3'; |
| 110 | + |
| 111 | +const machine = createMachine({ |
| 112 | + idle: state( |
| 113 | + transition('submit', 'processing', |
| 114 | + guard((ctx) => ctx.form.isValid) |
| 115 | + ) |
| 116 | + ), |
| 117 | + processing: state() |
| 118 | +}); |
| 119 | +``` |
| 120 | + |
| 121 | +### Guard Functions |
| 122 | + |
| 123 | +Guards are pure functions that receive the context and event, returning `true` to allow the transition or `false` to prevent it: |
| 124 | + |
| 125 | +```js |
| 126 | +const isValidUser = (ctx, event) => { |
| 127 | + return event.user && event.user.age >= 18; |
| 128 | +}; |
| 129 | + |
| 130 | +const machine = createMachine({ |
| 131 | + waiting: state( |
| 132 | + transition('login', 'authenticated', |
| 133 | + guard(isValidUser) |
| 134 | + ) |
| 135 | + ), |
| 136 | + authenticated: state() |
| 137 | +}); |
| 138 | +``` |
| 139 | + |
| 140 | +### Multiple Guards |
| 141 | + |
| 142 | +You can chain multiple guards - all must pass for the transition to occur: |
| 143 | + |
| 144 | +```js |
| 145 | +transition('submit', 'processing', |
| 146 | + guard(isValid), |
| 147 | + guard(hasPermission) |
| 148 | +) |
| 149 | +``` |
| 150 | + |
| 151 | +## ACTIONS |
| 152 | + |
| 153 | +<span id="actions"></span> |
| 154 | + |
| 155 | +Actions are side effects that occur during transitions. They let you update context, make API calls, update the DOM, or perform any other effects. |
| 156 | + |
| 157 | +```js |
| 158 | +import { createMachine, state, transition, action } from 'robot3'; |
| 159 | + |
| 160 | +const updateUser = action((ctx, event) => { |
| 161 | + return { ...ctx, user: event.user }; |
| 162 | +}); |
| 163 | + |
| 164 | +const machine = createMachine({ |
| 165 | + idle: state( |
| 166 | + transition('login', 'authenticated', updateUser) |
| 167 | + ), |
| 168 | + authenticated: state() |
| 169 | +}); |
| 170 | +``` |
| 171 | + |
| 172 | +### Action Types |
| 173 | + |
| 174 | +Robot supports different types of actions: |
| 175 | + |
| 176 | +#### Context Actions |
| 177 | +Update the machine's context: |
| 178 | + |
| 179 | +```js |
| 180 | +const increment = action((ctx) => ({ ...ctx, count: ctx.count + 1 })); |
| 181 | +``` |
| 182 | + |
| 183 | +#### Side Effect Actions |
| 184 | +Perform effects without changing context: |
| 185 | + |
| 186 | +```js |
| 187 | +const log = action((ctx, event) => { |
| 188 | + console.log('Transitioning with:', event); |
| 189 | + return ctx; // Return unchanged context |
| 190 | +}); |
| 191 | +``` |
| 192 | + |
| 193 | +#### Multiple Actions |
| 194 | + |
| 195 | +Chain multiple actions to execute in sequence: |
| 196 | + |
| 197 | +```js |
| 198 | +transition('submit', 'success', |
| 199 | + validate, |
| 200 | + saveToAPI, |
| 201 | + updateUI |
| 202 | +) |
| 203 | +``` |
| 204 | + |
| 205 | +### Action Timing |
| 206 | + |
| 207 | +Actions execute during the transition, after guards have passed but before entering the new state. This timing ensures: |
| 208 | +- Guards see the old context |
| 209 | +- The new state receives the updated context |
| 210 | +- Side effects don't occur if guards fail |
| 211 | + |
| 212 | +## Putting It All Together |
| 213 | + |
| 214 | +Here's a complete example using all core concepts: |
| 215 | + |
| 216 | +```js |
| 217 | +import { createMachine, state, transition, guard, action, interpret } from 'robot3'; |
| 218 | + |
| 219 | +// Actions |
| 220 | +const setUser = action((ctx, event) => ({ ...ctx, user: event.data })); |
| 221 | +const clearError = action((ctx) => ({ ...ctx, error: null })); |
| 222 | +const setError = action((ctx, event) => ({ ...ctx, error: event.error })); |
| 223 | + |
| 224 | +// Guards |
| 225 | +const isValid = guard((ctx, event) => event.data && event.data.email); |
| 226 | + |
| 227 | +// Machine definition |
| 228 | +const machine = createMachine({ |
| 229 | + idle: state( |
| 230 | + transition('fetch', 'loading', clearError) |
| 231 | + ), |
| 232 | + loading: state( |
| 233 | + transition('success', 'loaded', |
| 234 | + guard(isValid), |
| 235 | + setUser |
| 236 | + ), |
| 237 | + transition('failure', 'error', setError) |
| 238 | + ), |
| 239 | + loaded: state( |
| 240 | + transition('refresh', 'loading') |
| 241 | + ), |
| 242 | + error: state( |
| 243 | + transition('retry', 'loading', clearError) |
| 244 | + ) |
| 245 | +}, { |
| 246 | + user: null, |
| 247 | + error: null |
| 248 | +}); |
| 249 | + |
| 250 | +// Create and use service |
| 251 | +const service = interpret(machine, () => { |
| 252 | + console.log('Current state:', service.machine.current); |
| 253 | + console.log('Context:', service.context); |
| 254 | +}); |
| 255 | + |
| 256 | +// Trigger transitions |
| 257 | +service.send('fetch'); |
| 258 | +service.send({ type: 'success', data: { email: 'user@example.com' } }); |
| 259 | +``` |
| 260 | + |
| 261 | +This example demonstrates: |
| 262 | +- **States**: Four distinct states (idle, loading, loaded, error) |
| 263 | +- **Transitions**: Movement between states based on events |
| 264 | +- **Events**: Different triggers (fetch, success, failure, retry) |
| 265 | +- **Guards**: Validation before allowing transitions |
| 266 | +- **Actions**: Context updates and side effects |
| 267 | + |
| 268 | +These core concepts form the foundation of Robot's state management. Master these, and you'll be able to model complex application behavior in a clear, maintainable way. |
0 commit comments