skip to Main Content

I have a typeScript interface like below –

interface MovieStar {
  birth: ActorAddress;
  actorWiki: ActorDescription | ActorByMovie;
}

interface ActorAddress {
  city: string;
  state: string;
  country: string
}

interface ActorDescription {
  name: string;
  hobby: string;
}

interface ActorByMovie extends ActorDescription {
  movieName: string;
}

When I am trying to use the interface movieStar and access actorWiki, I am getting only common properties of ActorDescription and ActorByMovie and I am not able to access movieName.

My requirement: I am using React with TypeScript and I have assigned movieStar as a type for the data fetched from an API.

Now, I have another API the response of which is similar in structure, but has one extra property called movieName which I am not able to access.

Am I doing something wrong here or shall I structure my interface in some other way? I came across the concept of Discriminated Unions but I think it is useful when we want both scenarios in one component (discriminated based on type). Here, I want the interface with extra properties in a separate component itself.

2

Answers


  1. Based on your description, it seems like you’re encountering an issue due to the union type actorDescription | actorByMovie within your MovieStar interface. TypeScript will only allow you to access properties that are common to all types in the union when you try to access actorWiki. This is why you’re unable to access movieName directly, as it’s not a property on ActorDescription.

    Even though you mentioned that you might not need Discriminated Unions since you’re handling different scenarios in separate components, they could still offer a solution to your problem. The key here is to provide a way for TypeScript to differentiate between the two types at runtime, which can then be used to safely access properties specific to one type.

    To achieve this, you can introduce a discriminant property (commonly a type property) to your interfaces. Here’s how you can adjust your interfaces to use Discriminated Unions effectively:

    interface ActorAddress {
      city: string;
      state: string;
      country: string;
    }
    
    // Add a type property to both ActorDescription and ActorByMovie
    interface ActorDescription {
      type: 'actorDescription';
      name: string;
      hobby: string;
    }
    
    interface ActorByMovie extends ActorDescription {
      type: 'actorByMovie'; // Override the type property to differentiate
      movieName: string;
    }
    
    // Now, MovieStar can utilize the discriminated union for actorWiki
    interface MovieStar {
      birth: ActorAddress;
      actorWiki: ActorDescription | ActorByMovie;
    }
    

    With this setup, you can use a type guard to check the type property and TypeScript will then allow you to access properties specific to that type:

    function displayActorWiki(actorWiki: ActorDescription | ActorByMovie) {
      if (actorWiki.type === 'actorByMovie') {
        // TypeScript knows actorWiki is ActorByMovie here, so you can access movieName
        console.log(actorWiki.movieName);
      } else {
        // actorWiki is ActorDescription here
        console.log(actorWiki.name);
      }
    }
    

    This way, you can use the same MovieStar interface but still differentiate between the cases when you have additional properties to access. This approach keeps your types flexible and allows you to use them in different components as needed, while still maintaining type safety.

    Login or Signup to reply.
  2. Depending on your use case you might be able to simply narrow MovieStar["wiwactorWiki"] with the in-operator.

    if("movieName" in movieStar.actorWiki) {
        movieStar.actorWiki.movieName;
        //                  ^? (property) ActorByMovie.movieName: string
    }
    

    TypeScript Playground

    Another approach I find reasonable based on your problem description is to declare movieName as optional inside ActorDescription. That way you can save yourself an interface. movieName will be string | undefined but you can narrow it to string as explained above.

    interface MovieStar {
      birth: ActorAddress;
      actorWiki: ActorDescription
    }
    
    interface ActorAddress {
      city: string;
      state: string;
      country: string
    }
    
    interface ActorDescription {
      name: string;
      hobby: string;
      movieName?: string;
    }
    
    declare const movieStar: MovieStar;
    movieStar.actorWiki.movieName;
    //                  ^? (property) ActorDescription.movieName?: string | undefined
    

    TypeScript Playground

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search