Search code examples
typescriptinterfaceswitch-statement

Switch with specific type in TypeScript not working


I am trying to implement constants in an interface, but why does it give an error while accessing data in the switch case?

If I use just string in interface instead of constants APP_STATUS then it works fine.

Example:

// Gives an error
interface InconsistenciesData {
  type: typeof APP_STATUS.INCONSISTENCIES;
  data: Inconsistency[];
}

// Works fine
interface InconsistenciesData {
  type: 'INCONSISTENCIES';
  data: Inconsistency[];
}

Below are my code snippets.

File types.ts

export const APP_STATUS = {
  CONFIRMED: 'CONFIRMED',
  INCONSISTENCIES: 'INCONSISTENCIES',
  SUCCESS: 'SUCCESS',
  ERROR: 'ERROR',
  LOADING: 'LOADING',
  OK: 'OK'
}

interface InconsistenciesLoading {
  type: typeof APP_STATUS.LOADING;
}

interface InconsistenciesError {
  type: typeof APP_STATUS.ERROR;
}

interface InconsistenciesSuccess {
  type: typeof APP_STATUS.SUCCESS;
}

interface InconsistenciesData {
  type: typeof APP_STATUS.INCONSISTENCIES;
  data: Inconsistency[];
}

export type ViewState = InconsistenciesData | InconsistenciesSuccess | InconsistenciesLoading | InconsistenciesError;

My React component

const [viewState, setViewState] = useState<ViewState>({type: APP_STATUS.LOADING})
const renderPageContent = () => {
  switch (viewState.type) {
    case APP_STATUS.INCONSISTENCIES:
      return <InconsistenciesTable inconsistencies={viewState.data} />  //Error: Property 'data' does not exist on type 'ViewState'. Property 'data' does not exist on type 'InconsistenciesSuccess'.
    case APP_STATUS.ERROR:
      return <Forbidden />
    case APP_STATUS.SUCCESS:
      return <ThankYouContent />
    case APP_STATUS.LOADING:
      return <Loading />
  }
}

Solution

  • Just replace your object with an enum, that's what they're for:

    export enum APP_STATUS {
      CONFIRMED,
      INCONSISTENCIES,
      SUCCESS,
      ERROR,
      LOADING,
      OK,
    }
    
    export interface Inconsistency {};
    
    export interface InconsistenciesLoading {
      type: APP_STATUS.LOADING;
    }
    
    export interface InconsistenciesError {
      type: APP_STATUS.ERROR;
    }
    
    export interface InconsistenciesSuccess {
      type: APP_STATUS.SUCCESS;
    }
    
    export interface InconsistenciesData {
      type: APP_STATUS.INCONSISTENCIES;
      data: Inconsistency[];
    }
    
    type ViewState = InconsistenciesData | InconsistenciesSuccess | InconsistenciesLoading | InconsistenciesError;
    
    export const component = ({ state }: { state: ViewState }) => {
        switch (state.type) {
            case APP_STATUS.INCONSISTENCIES:
                console.log(state.data); // Look ma, no error!
        }
    };
    

    Playground

    Your version fails because unlike an enum your APP_STATE constant is actually just a regular mutable object: there's no guarantee that your compile-time type will still hold when the switch statement actually takes effect at runtime.