State-driven interfaces with XState

Hey FriEnds đź‘‹

Diky Arga

FE Dev - PandaCare

What's State?

A state is a representation of a system in a given time. State refers to the data stored in Application in the form of a string, arrays, object, etc.

ex. is user logged-in?, is Loading?, is Error?

Basically, we as a frontend dev is getting paid to manage states

isLoading?

isError?

isAbela?

How do we manage the state?

basic fetch and set data

loading too long, add loading indicator

sometime we don't get what we want, display error

sometime we have moody user, who suddenly want to cancel the request

What could go wrong?

  • What if fetch fails?
  • What if the user taps the button twice?
  • What if the user wants to cancel the fetch request?
  • Disable the button when fetching
  • Show loading progress
  • Show error message
  • What if the request is restricted/blocked?
  • And so on...

The problems with

Event-Driven Interfaces

  • The code doesn’t accurately represent our component’s intended behavior
    boolean variable like isLoading, isError, isSuccess, isMail is hard to manage and error-prone
  • Forgot to add guards
  • Not declarative
    what to do with isSuccess?
  • No separation of concerns
    Spaghetti  code in a component

A batter way (hopefully)

State-Driven Interfaces

Finite State Machine

FSM

What is?

FSM is everywhere

FSM for developing UI?

XState

 JavaScript state machines and statecharts

by David K Piano

Let's simplify our life

by rewriting our owsem app with state machine

Advantages

  • Declarative (named state, listed all possible states)
  • Easier to test
  • Separation of concerns (view, logic, data fetch)
  • Framework Agnostic
  • Visualization
  • A common language between developer and designer

The cool things about XState

XState Vizualizer

yarn add @xstate/fsm

Only 1 KB

Do we really need library like XState?

No

function dogReducer(state, event) {
  switch (event.type) {
    case "FETCH":
      return {
        ...state,
        status: "loading"
      };
    case "RESOLVE":
      return {
        ...state,
        status: "success",
        dog: event.data
      };
    case "REJECT":
      return {
        ...state,
        status: "failure",
        error: event.error
      };
    case "CANCEL":
      return {
        ...state,
        status: "idle"
      };
    default:
      return state;
  }
}

const initialState = {
  status: "idle",
  dog: null,
  error: null
};

you can use reducer

No

function DogFetcher() {
  // 'idle' or 'loading' or 'success' or 'error'
  const [status, setStatus] = useState('idle');
}

you can use as simple as useState

Already miss isLoading?

function DogFetcher() {
  // 'idle' or 'loading' or 'success' or 'error'
  const [status, setStatus] = useState('idle');

  const isLoading = status === 'loading';

  return (
    // ...
    <button disabled={isLoading}>
      {/* ... */}
    </button>
    // ...
  );
}

Trade-off with State Machine

talkMachine.send('done')

<ThankYou/>

talkMachine.send('QnATime')

references

State-driven interfaces with XState

By Diky Arga

State-driven interfaces with XState

  • 280