Search code examples
typescripttypescript-typingsreact-typescript

TS Type guard doesn't detect a non-empty class object property


I have the following problem with TypeScript. There is a class property of the following type AmplitudeFeatureFlags | {} = {}; and equals an empty object by default. I have a method that receives a property name arg of enum type to retrieve some value from the class property shown above. But TS shows the error described in the code below.

What am I doing wrong?

export enum AmplitudeFeatureFlagNames {
  addTeamMembersFromUserMenu = 'addTeamMembersFromUserMenu',
  inAppHandRaiser = 'inAppHandRaiser',
}

type AmplitudeFeatureFlagWithPayload<Payload extends object = {}> = {
  key: string;
  payload?: Payload;
};

export type AmplitudeFeatureFlags = {
  [AmplitudeFeatureFlagNames.addTeamMembersFromUserMenu]: AmplitudeFeatureFlagWithPayload;
  [AmplitudeFeatureFlagNames.inAppHandRaiser]: AmplitudeFeatureFlagWithPayload;
};

class User {
  private amplitudeFeatureFlags: AmplitudeFeatureFlags | {} = {};

    getAmplitudeFeatureFlagPayload(flagName: AmplitudeFeatureFlagNames) {
      if (flagName in this.amplitudeFeatureFlags) {
    
    **//ERROR on the line below:**
    //Element implicitly has an 'any' type because expression of type 
   //'AmplitudeFeatureFlagNames' can't be used to index type '{} | 
    //AmplitudeFeatureFlags'.
    //Property '[AmplitudeFeatureFlagNames.addTeamMembersFromUserMenu]' does not exist on 
    //type '{} | AmplitudeFeatureFlags'.

        **return this.amplitudeFeatureFlags[flagName]?.payload;**
      }
  }
}

Solution

  • It's because this.amplitudeFeatureFlags is not always of type AplitudeFeatureFlags. Your declaration is

    private amplitudeFeatureFlags: AmplitudeFeatureFlags | {}

    So typescript knows it might be of type {}, and thus {}[key: AmplitudeFeatureFlagNames] is of type any implicitly.

    You'll want to change the declaration to something more descriptive:

    private amplitudeFeatureFlags: Partial<AmplitudeFeatureFlags> = {}

    Making your code:

    export enum AmplitudeFeatureFlagNames {
      addTeamMembersFromUserMenu = 'addTeamMembersFromUserMenu',
      inAppHandRaiser = 'inAppHandRaiser',
    }
    
    type AmplitudeFeatureFlagWithPayload<Payload extends object = {}> = {
      key: string;
      payload?: Payload;
    };
    
    export type AmplitudeFeatureFlags = {
      [AmplitudeFeatureFlagNames.addTeamMembersFromUserMenu]: AmplitudeFeatureFlagWithPayload;
      [AmplitudeFeatureFlagNames.inAppHandRaiser]: AmplitudeFeatureFlagWithPayload;
    };
    
    class User {
      private amplitudeFeatureFlags: Partial<AmplitudeFeatureFlags> = {};
    
        getAmplitudeFeatureFlagPayload(flagName: AmplitudeFeatureFlagNames): AmplitudeFeatureFlagWithPayload['payload'] | undefined {
          if (flagName in this.amplitudeFeatureFlags) {
            return this.amplitudeFeatureFlags[flagName]?.payload;
          }
          return undefined
      }
    }