📄
react-states
  • react-states API
  • Introduction
  • Our journey
  • The Mental Model
  • Adopting react-states
  • Reference implementations
  • Environment interface
  • Subscriptions
  • Features
  • Writing UI components
  • Explicit states
  • State transitions
  • Transition effects
  • Feature testing
  • Files and folders
  • Patterns
  • PR review guide
  • Devtools
Powered by GitBook
On this page
  • Base state
  • Nested states

Was this helpful?

Explicit states

The states of a feature is explicit. That means we have an explicit state property which holds the name of a given state the feature can be in, with any related values.

export type State =
  | {
      state: 'UNAUTHENTICATED'
    }
  | {
      state: 'AUTHENTICATING'
    }
  | {
      state: 'AUTHENTICATED'
      user: { name: string }
    }
  | {
    state: 'ERROR'
    error: string
  }

Thinking in terms of explicit states is a mind shift. You will spend a lot more time identifying what unique states each piece of the flow represents and how to properly name them.

The name of a state should ideally fit with the sentence "I am $STATE". Imagine the feature telling you what state it is in. This can sometimes feel unnatural and a state like ERROR might pop up, which is perfectly fine. The important thing is that it is not imperative, like AUTHENTICATE or SIGN_IN.

The explicit states are expressed and transitioned with a reducer.

import { createReducer, States } from 'react-states'

export type State =
  | {
      state: 'UNAUTHENTICATED'
    }
  | {
      state: 'AUTHENTICATING'
    }
  | {
      state: 'AUTHENTICATED'
      user: { name: string }
    }
  | {
    state: 'ERROR'
    error: string
  }
  
export type PublicAction = {
  type: 'AUTHENTICATE'
}

export type Feature = States<State, PublicAction>
  
const reducer = createReducer<Feature>({
  UNAUTHENTICATED: {},
  AUTHENTICATING: {},
  AUTHENTICATED: {},
  ERROR: {}
})
  
export const FeatureProvider = () => {
  const feature = useReducer(reducer, {
    state: 'UNAUTHENTICATED'
  })
  ...
}

Base state

Some values might occur in multiple states. In this situation you can create a BaseState which is composed with two are more states.

type BaseState = {
  data: any[]
}

type State =
  | {
      state: 'NOT_LOADED'
    }
  | {
      state: 'LOADING'
    }
  | (BaseState & (
      | {
        state: 'LOADED'
      }
      | {
        state: 'SYNCING'
      }
      | {
        state: 'SYNCED'
      }
    ))

Nested states

You do not have to express the whole state at the root, you can split it up into nested states.

type ValidationState =
  | {
      state: 'VALID';
    }
  | {
      state: 'INVALID';
    }
  | {
      state: 'PENDING';
    };

type State =
  | {
      state: 'ACTIVE';
      value: string;
      validation: ValidationState;
    }
  | {
      state: 'DISABLED';
    };

Now any use of match can be done on the sub states as well.

match(state, {
  DISABLED: () => ({}),
  ACTIVE: ({ validation }) =>
    match(validation, {
      VALID: () => ({}),
      INVALID: () => ({}),
      PENDING: () => ({}),
    }),
});
PreviousWriting UI componentsNextState transitions

Last updated 3 years ago

Was this helpful?