Infinitely Better UIs
with

David Khourshid - Microsoft

@davidkpiano

Finite Automata

Developing user interfaces

is not easy

Deterministic Finite Automata

(Finite State Machines)

  • Combinatorial logic

  • Finite state machines

  • Pushdown automata

  • Turing machines

  • Configuring Webpack

Automata theory

React makes us

rethink

class GoatButton extends Component {
  state = {
    goat: undefined
  };

  fetchGoat = () => {
    fetch('url/to/goat')
      .then(goat => this.setState({ goat }));
  }
  
  render() {
    const { goat } = this.state;
    
    return (
      <div>
        {goat ? <img src={goat} /> : null}
        <button onClick={this.fetchGoat}>
          Fetch Goat
        </button>
      </div>
    );
  }
}
  • Goat search fails?
  • Button clicked repeatedly?
  • Disable button
  • Loading message
  • Error message

Set goat in state

Handle fetching the goat

Render goat button and image


  state = {
    goat: undefined,
    goatError: false,
    goatSearching: false
  };

Add boolean flags


  fetchGoat = () => {
    if (this.state.goatSearching) return;

    this.setState({
      goatSearching: true,
      goatError: false
    });

    fetch('url/to/goat')
      .then(goat => this.setState({
        goat,
        goatSearching: false
      }))
      .catch(err => this.setState({ goatError: true });
  }

Don't search if already searching

Indicate an error


  render() {
    const { goat, goatError, goatSearching } = this.state;
    
    return (
      <div>
        {goat && <img src={goat} />}
        {goatError && <span>Error: goat not found</span>}
        <button onClick={this.fetchGoat} disabled={goatSearching}>
          {goatSearching
            ? 'Searching...'
            : goatError
              ? 'Goat fail, retry?'
              : 'Fetch goat'
          }
        </button>
      </div>
    );
  }

Disable button if loading

Custom button text

class GoatButton extends Component {
  state = {
    goat: undefined
  };

  fetchGoat = () => {
    fetch('url/to/goat')
      .then(goat => this.setState({ goat }));
  }
  
  render() {
    const { goat } = this.state;
    
    return (
      <div>
        {goat ? <img src={goat} /> : null}
        <button onClick={this.fetchGoat}>
          Fetch Goat
        </button>
      </div>
    );
  }
}
class GoatButton extends Component {
  state = {
    goat: undefined,
    goatError: false,
    goatSearching: false
  };

  fetchGoat = () => {
    if (this.state.goatSearching) return;

    this.setState({
      goat: undefined,
      goatSearching: true,
      goatError: false
    });

    fetch('url/to/goat')
      .then(goat => this.setState({ goat, goatSearching: false }))
      .catch(err => this.setState({ goatError: true });
  }
  
  render() {
    const { goat, goatError, goatSearching } = this.state;
    
    return (
      <div>
        {goat && <img src={goat} />}
        {goatError && <span>Error: goat not found</span>}
        <button onClick={this.fetchGoat} disabled={goatSearching}>
          {goatSearching
            ? 'Searching...'
            : goatError
              ? 'Goat fail, retry?'
              : 'Fetch goat'
            }
        </button>
      </div>
    );
  }
}

Before

Aw πŸ’©

Bottom-up

πŸ‘πŸ†™

approach

Event

Action C

Action A

Action E

Action B

Action D

Business logic

πŸ‘πŸ†™
Code

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

  • props

  • setState()

  • props

  • setState()

  • callback props

  • context

  • Flux

  • Redux

  • MobX

  • RxJS

  • monads?

User interfaces are

(Trees are graphs, too)

graphs

Deterministic

Finite

Automata

Deterministic

Finite

Automata

Number of possible states

Deterministic

Finite

Automata

Predetermined sequence

Deterministic

Finite

Automata

State + action = next state, always

Deterministic

Finite

Automata

State Machines

Designing

state machines

It's time for some

game

graph theory

PILL

TIMER

EAT

REVIVE

An initial state

A finite number of states

Transitions between states

Actions that cause transitions

idle

loading

goat

error

CLICK

RESOLVE

REJECT

CLICK

CLICK

State Transition Diagram

STDΒ just call it this πŸ‘†

State machines provide a

common language

for designers & developers.

Developing

state machines

  • idle -> loading
  • loading -> goat -> error
  • error -> loading
  • goat -> loading

Adjacency list


const machine = {
  idle: {
    CLICK: 'loading'
  },
  loading: {
    RESOLVE: 'goat',
    REJECT: 'error',
  },
  goat: {
    CLICK: 'loading'
  },
  error: {
    CLICK: 'loading'
  }
};

const initialState = 'idle';

const machine = {
  // ...
};

const initialState = 'idle';



function transition(state, action) {
  return machine[state][action];
}
const t=(m,s,a)=>m[s][a]

state = {
  goatState: initialState, // 'idle'
  goat: undefined
};
commands = {
  loading: this.fetchGoat
}

Finite state

Data

Commands (side effects)


transition = (action) => {
  const { goatState } = this.state;


  const nextState = machine[goatState][action];


  const command = this.commands[nextState];


  this.setState({
    goatState: nextState
  }, command);
}

Ryan

state

Transition function!

Next command

Set state, then exec command


fetchGoat = () => {
  fetch('url/to/goat')
    .then(goat => this.setState({ goat },
      () => this.transition('RESOLVE')
    ))
    .catch(_ => this.transition('REJECT'));
}

render() {
  const { goat, goatState } = this.state;
  const buttonText = {
    idle: 'Fetch goat'
    loading: 'Loading...',
    error: 'Goat fail, retry?',
    goat: 'Fetch another goat'
  }[goatState];
  
  return (
    <div>
      {goat && <img src={goat} />}
      {goatState === 'error' && <span>Error: goat not found</span>}
      <button
        onClick={() => this.transition('CLICK')}
        disabled={goatState === 'loading'}>
        {buttonText}
      </button>
    </div>
  );
}

Declarative rendering

Testing & Visualizing

state machines

oh god conference wifi

with FSMs

Trade-offsΒ 

πŸ’₯ State explosion πŸ’₯

Statecharts

Hierarchical finite state machines

Hierarchical states

Concurrent states

History states

XState

Announcing

npm install xstate


import { Machine } from xstate;

const machine = Machine({ ... });

const nextState = machine
  .transition(currentState, action);

states:

green

on:

yellow

red

states:

TIMER

yellow

on:

TIMER

red

walk ...

wait ...

stop ...

on:

TIMER

green

XState

Announcing

npm install xstate


lightMachine
  .transition('red.stop', 'TIMER')
  .toString();

// 'green'


lightMachine
  .transition('yellow', 'TIMER')
  .value;

// { red: 'walk' }

Hierarchical states

XState

Announcing

npm install xstate


textMachine
  .transition('bold.off', 'TOGGLE_BOLD')
  .value;

// {
//   bold: 'on',
//   italics: 'off',
//   underline: 'off',
//   list: 'bullets'
// }

Concurrent states

  • Precise semantics
  • Rich, expressive notation
  • Easily understandable
  • Fast to create
  • Easy to achieve complete UI specs

Resources

Advantages

Thank you,Β 

React Rally!

David Khourshid

@davidkpiano

Infinitely Better UIs with Finite Automata

By David Khourshid

Infinitely Better UIs with Finite Automata

  • 36,907