@davidkpiano · FEDC Taiwan 2018

The (Finite) State

of Reactive Animations

I live here

The most neglected variable is time.

📞

🔙

🙏🤐

🅰️⚓️

🅰️🏋️‍♀️

🔭

🅰️

⚽️🎾🎱

Callbacks

Promises

Async-Await

Observables

(Continuous) events

Discrete changes

A "reactive animation" is one involving discrete changes, due to events.

[

]

. . .

Value

Array

Promise

Observable

3s

1s

2s

3.5s

import { fromEvent } from 'rxjs';
import { map } from 'rxjs/operators';

const bodyElement = document.querySelector('body');

const mouseMove$ = fromEvent(body, 'mousemove');

// Stream of mouse positions
const mousePos$ = mouseMove$.pipe(
  map(event => ({
    x: event.clientX,
    y: event.clientY
  }))
);

Creating Observables

npm install rxjs --save






mouseMove$.subscribe((value) => {
  // do anything with the value
});


mousePos$.subscribe((value) => {
  const { x, y } = value;

  bodyElement.style
    .setProperty('--mouse-x', x);
  bodyElement.style
    .setProperty('--mouse-y', y);
});

Subscribing to Observables

For more information:

CSS Variables!

How can we model the behavior

of user interfaces?

API

Human

  • documented

  • predictable

  • testable

  • un

  • un

  • un

#Frameworkless

Any sufficiently complicated model class contains an ad-hoc, informally-specified, bug-ridden, slow implementation of
half of a state machine.

state management library

State machine...?

// ...
onValidate(password) {
  fetch(API + '&password=' + password)
    .then(data => this.setState({ data }));
}
// ...

Show data when results retrieved

// ...
onValidate(password) {
  this.setState({ loading: true });

  fetch(API + '&password=' + password)
    .then(data => {
      this.setState({ data, loading: false });
    });
}
// ...

Show loading screen

Show data when results retrieved

Hide loading screen

// ...
onValidate(password) {
  this.setState({ loading: true });

  fetch(API + '&password=' + password)
    .then(data => {
      this.setState({ data, loading: false });
    })
    .catch(error => {
      this.setState({
        loading: false,
        error: true
      });
    });
}
// ...

Show loading screen

Show data when results retrieved

Hide loading screen

Show error

Hide loading screen

// ...
onValidate(password) {
  this.setState({
    loading: true,
    error: false
  });

  fetch(API + '&password=' + password)
    .then(data => {
      this.setState({
        data,
        loading: false,
        error: false
      });
    })
    .catch(error => {
      this.setState({
        loading: false,
        error: true
      });
    });
}
// ...

Show loading screen

Show data when results retrieved

Hide loading screen

Show error

Hide loading screen

Hide error

Hide error

// ...
onValidate(password) {
  if (this.state.loading) return;

  this.setState({
    loading: true,
    error: false,
    canceled: false
  });

  fetch(API + '&password=' + password)
    .then(data => {
      if (this.state.canceled) {
        return;
      }

      this.setState({
        data,
        loading: false,
        error: false
      });
    })
    .catch(error => {
      // 什么他妈的
      if (this.state.canceled) {
        return;
      }

      this.setState({
        loading: false,
        error: true
      });
    });
}

onCancel() {
  this.setState({
    loading: false,
    error: false,
    canceled: true
  });
}
// ...

Show loading screen

Show data when results retrieved

Hide loading screen

Show error

Hide loading screen

Hide error

Hide error

Validation in progress already

Cancel cancellation

Ignore results if cancelled

Ignore error if cancelled

Cancel search

        error: false
      });
    })
    .catch(error => {
      // 什么他妈的
      if (this.state.canceled) {
        return;
      }

      this.setState({
        loading: false,
        error: true
      });
    });
}

🍑 🆙

The bottom-up

EVENT

  • ACTION 1
  • ACTION 2
  • ACTION 3
  • ACTION 4
  • ACTION 5
  • ACTION 6

approach

State

🍑 🆙
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

Intuition

  • UI components are not independent
  • Actions are based on event & state
  • The event-action paradigm is too simple

Finite state machines

and statecharts

Finite state machines

  • 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

Idle

Searching...

SEARCH

Success

Failure

RESOLVE

REJECT

SEARCH

SEARCH

SEARCH

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' }
    }
  }
};
function transition(state, event) {
  return machine.states[state].on[event];
}

Define transitions between
states & actions

Transition function determines
next state from state + event

State machines in 

VS Live Share

Signed out

Signing in

Signed in

SIGN IN

SIGN IN SUCCESS

SIGN IN FAILURE

Sharing...

Shared

Joining...

Joined

share

share Success

Join Success

Join

Leave

End Collab session

Sign in

Using state machines

for analytics

transition(currentState, event) {
  const nextState = // ...

  Telemetry.sendEvent(
    currentState,
    nextState,
    event
  );

  return nextState;
}

Using state machines

for analytics

A

B

C

D

E

Using state machines

for integration testing

  • Shortest path algorithms (Dijkstra, Bellman-Ford, A* search, etc.)
  • Analytics provides weights
  • Represents all happy paths
  • Can be automatically generated

A

B

C

D

E

A → B
A → B → C
A → D
A → D → E

Using state machines

for integration testing

  • Depth-first search (DFS) algorithm for finding all simple paths
  • Represents all possible user flows
  • Reveals all edge cases
  • Can be automatically generated ⚠️

A

B

C

D

E

A → B
A → B → C
A → D
A → D → E
A → D → E → C
A → D → B → C
A → B → E → C
A → D → E → B → C

Software bugs are

made visually clear

idle

loading

success

failure

FETCH

RESOLVE

ERROR...?

Error

RETRY

Does this scale?

Harel Statecharts

extended finite state machines

Statecharts

Idle

Searching...

onEntry / prefetchResources

onEntry / fetchResults

Search

  • Actions - onEntry, onExit, transition
  • Guards - conditional transitions

[query.length > 0]

Statecharts

  • Actions - onEntry, onExit, transition
  • Guards - conditional transitions
  • Hierarchy - nested states
  • Orthogonality - parallel states
  • History - remembered states

H

Bold ON

Bold OFF

Italics ON

Italics OFF

Underline ON

Underline OFF

Characters

Idle

Searching...

SEARCH

Success

Failure

RESOLVE

REJECT

SEARCH

SEARCH

SEARCH

Idle

Searching...

SEARCH

Success

Failure

RESOLVE

REJECT

SEARCH

SEARCH

Searched

Statecharts

with xstate

npm install xstate --save
  • Actions - onEntry, onExit, transition
  • Guards - conditional transitions
  • Hierarchy - nested states
  • Orthogonality - parallel states
  • History - remembered states
const lightMachine = Machine({
  initial: 'green',
  states: {
    green: {
      on: {
        TIMER: 'yellow'
      }
    },
    yellow: {
      on: {
        TIMER: 'red'
      }
    },
    red: {
      on: {
        TIMER: 'green'
      }
    }
  }
});

const nextState = lightMachine
  .transition('green', 'TIMER');

// State {
//   value: 'yellow' 
// }
const lightMachine = Machine({
  initial: 'green',
  states: {
    green: {
      on: {
        TIMER: 'yellow'
      }
    },
    yellow: {
      onEntry: ['activateYellow']
      on: {
        TIMER: 'red'
      }
    },
    red: {
      onExit: ['stopCountdown']
      on: {
        TIMER: 'green'
      }
    }
  }
});
const lightMachine = Machine({
  initial: 'green',
  states: {
    green: {
      on: {
        TIMER: {
          yellow: {
            cond: (xs, event) =>
              event.elapsed > 10000
          }
        }
      }
    },
    yellow: {
      on: {
        TIMER: 'red'
      }
    },
    red: {
      on: {
        TIMER: 'green'
      }
    }
  }
});
const lightMachine = Machine({
  initial: 'green',
  states: {
    green: {
      on: {
        TIMER: 'yellow'
      }
    },
    yellow: {
      on: {
        TIMER: 'red'
      }
    },
    red: {
      initial: 'walk',
      states: {
        walk: {
          { on: { PED_COUNTDOWN: 'wait' } }
        },
        wait: {
          { on: { PED_COUNTDOWN_END: 'stop' } }
        },
        stop: {}
      }
    }
  }
});
const lightsMachine = Machine({
  parallel: true,
  states: {
    northSouthLight: {
      initial: 'green',
      states: {
        // ...
      }
    },
    eastWestLight: {
      initial: 'red',
      states: {
        // ...
      }
    }
  }
});
const payMachine = Machine({
  initial: 'method',
  states: {
    method: {
      initial: 'card',
      states: {
        card: {
          on: { SELECT_CASH: 'cash' }
        },
        cash: {
          on: { SELECT_CARD: 'card' }
        }
      },
      on: {
        NEXT: 'review'
      }
    },
    review: {
      on: {
        PREV: 'method.$history'
      }
    }
  }
});
const machine = Machine({ /* ... */ });

let currentState = machine.initialState;

// ...

function send(event) {
  const nextState = machine.transition(currentState, event);
  const { actions } = nextState;

  // Set the next state
  currentState = nextState;

  // Execute each action
  actions.forEach(action => action());
}

machine.transition() is
just a reducer function!

Advantages

of using statecharts

  • Visualized modeling
  • Precise di​agrams
  • Automatic code generation
  • Comprehensive test coverage
  • Accommodation of late-breaking requirements changes

Disadvantages

of using statecharts

Learning curve

Modeling requires planning ahead

Not everything can be modeled (yet)

Statecharts

FSMs

Bottom-up

Complexity

trade-offs

States & logic

Code Complexity

Statechart
visualization?

BETA!

The future of xstate

  • Full SCXML support and conversion
  • A reactive interpreter
  • Editable visualization tools
  • Analysis and testing tools

Resources

and tools

Write once, write anywhere

Learn once, write anywhere

Model once, implement anywhere

Make your code do more with state machines.

Thank you FEDC Taiwan!

@davidkpiano · FEDC Taiwan 2018

The Finite State of Reactive Animations

By David Khourshid

The Finite State of Reactive Animations

FEDC Taiwan 2018

  • 945
Loading comments...

More from David Khourshid