Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Taming UI complexity with Typed State Machines

Taming UI complexity with Typed State Machines

Managing state in UI apps is hard. Keeping a coherent mental model of it is even harder.

Finite State Machines seem to offer a good abstraction of the way we interact with UIs, making states and transitions between them explicit, and thus easier to reason about.

One of the nice things about FSMs is that only certain transitions are legal from each state. But is it possible to enforce that in code? It seems that, with some TypeScript magic, it is possible.

In this talk, you'll be exposed to a new perspective on implementing FSMs in JS/TS and how it compares to libraries such as XState or Redux (not really a FSM, but similar). You'll also see how this new approach can be used in real apps, using various libraries, such as React, Vue, and others, some of them quite unexpected.

Bogdan Zaharia

April 12, 2019
Tweet

More Decks by Bogdan Zaharia

Other Decks in Programming

Transcript

  1. ‣ Software developer @ ‣ Doing React with Typescript @work

    ‣ Played with Elm @home ABOUT ME BOGDAN ZAHARIA @zaboco
  2. POTENTIALLY ASKED QUESTIONS ‣ Do I need to know about

    state machines? ‣ NO ‣ Do I need to know Typescript? ‣ A LITTLE PAQ
  3. STATES ‣ Idle 1. INSERT_CAN ‣ Loaded 2. PRESS_BUTTON ‣

    Recycling 2.5 WAIT_FOR_RECYCLING ‣ Done 3. COLLECT_REWARD ‣ Idle (again)
  4. Idle Loaded INSERT_CAN Recycling PRESS_BUTTON Done WAIT_FOR_RECYCLING COLLECT_REWARD REAL PRESS_BUTTON

    COLLECT_REWARD " WAIT_FOR_RECYCLING PRESS_BUTTON PRESS_BUTTON INSERT_CAN INSERT_CAN INSERT_CAN
  5. WHAT ABOUT XSTATE? ‣ Great library, a lot of features.

    ‣ Can specify states and transitions: const recyclingMachine = Machine({ initial: "Idle", states: { Idle: { on: { INSERT_CAN: "Loaded" } }, "// ""... } }); ‣ Written in Typescript ‣ Does not enforce transitions recyclingMachine.transition('INSERT_CAN') "// Loaded recyclingMachine.transition('INSERT_CAN') "// still Loaded
  6. WHAT ABOUT REDUX? What about it? Redux is not a

    state machine Or is it? Maybe Redux is a machine with 1 state.
  7. function reducer( model: AppModel = {}, action: AppAction ): AppModel

    { switch (action.type) { case 'LOGIN_SUCCESS': return { name: action.payload }; case 'LOGIN_ERROR': return { error: action.payload }; case 'LOGOUT': return {}; case 'NAME_TO_LOWERCASE': return { name: model.name.toLowerCase() } } } OUR REDUX APP type AppModel = { error"?: string; name"?: string; }; type AppAction = | { type: 'LOGIN_SUCCESS'; payload: string; } | { type: 'LOGIN_ERROR'; payload: string; } | { type: 'NAME_TO_LOWERCASE' } | { type: 'LOGOUT' }; ❗
  8. LoggedOut { error!?: string } LoggedIn { name: string }

    LOGIN_SUCCESS (string) LOGIN_ERROR (string) LOGOUT NAME_TO_LOWERCASE
  9. View HOW UI IS HANDLED IN REDUX Store uses the

    model dispatch Machine transition OR XSTATE
  10. LoggedIn View WHAT IF WE INVERT THE CONTROL? Machine {name:string}

    LOGOUT or NAME_TO_LOWERCASE LoggedOut View {error!?:string} LOGIN_SUCCESS or LOGIN_ERROR
  11. NOT ONLY REACT ‣ A machine is abstract ‣ Needs

    adapters (e.g. MachineAdapter for React) ‣ Same machine used with different views/adapters ‣ Other implemented (more or less) adapters: ✓ Vue.js ✓ CLI ✓ Koa (server-side)
  12. NEXT STEPS ๏ Update docs ๏ Scaling - machines inside

    machines (as model) [WIP] ๏ Async transitions (e.g. fetch requests) [WIP] x Routing x History - undo/redo x Time travel debugging x Eslint plugin (multiple dispatches, unreachable states)
  13. WHAT IF I DON’T WANT TO USE TYPESCRIPT ‣ TS

    is still JS, so typed-machine can be used with JS - (all the type guarantees are lost) ‣ Try XState or others ‣ Port typed-machine to Flow or Reason