2

I am very new in Typescript and I have an issue which I don't know how to resolve it :(.

Basically I want to create a list of tuple from a list of components. The first element of the tuple is the element's name (keyof MyComponents) and the second elements is its attributes.

(See code below)

playground link

 interface MyComponents  {
    Container: {
      fluid?: boolean
      className?: string
    },
    Tag: {
      text?: string
      className?: string
      hidden: boolean
    }
}

//Get the keys of the list of my components
type Element = keyof MyComponents
 
//Get the attributes depending on the element name
type PickAttributes<T extends Element> = Pick<MyComponents[T], keyof MyComponents[T]>

//Create a mapped tuple type [Element, Attributes]
// and the attributes depend on the element
export type Tuple = { 

  [Element in keyof MyComponents] : [Element, PickAttributes<Element>]

}[keyof MyComponents]

 

const attr : PickAttributes<'Tag'> = {hidden : false} //This works and the auto completion works perfectly

const tuple1 : Tuple = ["Tag", { hidden: false}] //This also works

const tuple2 : Tuple = ["Container", { hidden: false}] //Error but it's normal as the element 'Container' doesn't have the property hidden:boolean

Everything works perfectly but there is a small problem for autocompletion. When I type the first element (Container, Tag, ...), the auto completion of the second element (its attributes) shows all the possible attributes even the wrong ones.

As an example if I type 'Tag' for the first element it suggets me 'fluid' but 'fluid' is only available in 'Container' ! Intellisense shows all options

And when I choose fluid, it also knows it's incompatible...

Typescript knows it's incompatible

So my question is: How can I restrict the autocomplete to only shows valid attributes depending on the element's name ?

Any help will be appreciated ! Thanks !**

ddStack
  • 31
  • 1
  • 3
  • I don't have an answer, but autocomplete has to work when your code isn't even completely syntacticly valid. So I imagine that its safety is highly reduced compared to the full type checker. – Alex Wayne Apr 23 '21 at 00:21
  • Your code is correct. You can write the tuple as `[Element, MyComponents[Element]]` instead of `[Element, PickAttributes]` but that doesn't change anything. The autocomplete I think is just something that you have to live with. – Linda Paiste Apr 23 '21 at 06:14
  • Thanks @AlexWayne for you response. Yes it seems it's the case here :( – ddStack Apr 23 '21 at 13:08
  • Thank you @LindaPaiste for your response appreciate it :) I'll try if I can come up with an another solution with this specific case. – ddStack Apr 23 '21 at 13:10
  • I would guess it's related to [this issue](https://github.com/Microsoft/TypeScript/issues/29729) specifically -- TypeScript is simplifying/flattening the type for performance reasons in the autocomplete. Forcing a generic appears to work ie `export type Tuple = [T, MyComponents[T]]; const test3: Tuple<"Tag"> = ["Tag", {}]` – Zwei Apr 23 '21 at 19:16
  • @Zweihänder Thanks for your response. But then how can I define an array of Tuple<> ? I think I can't do something like that : const test4 : Tuple [] = ... – ddStack Apr 23 '21 at 19:40

1 Answers1

0

I can't speak to your title question as to why intellisense does that, but I can give you two options to answer the question "How can I restrict the autocomplete to only shows valid attributes depending on the element's name?":

Option 1, Use objects instead of arrays to model the tuple:

export type TupleAsObject = {
                               // If desired, the keys can now be more descriptive
  [Element in keyof MyComponents] : { 0: Element, 1: PickAttributes<Element> }
}[keyof MyComponents]
const test3 : TupleAsObject = {0: "Tag", 1: { /* type will be narrowed correctly */ }}
  • Pros: It does what you want, and allows you to be more descriptive with property keys.
  • Cons: It's no longer an array, so if you are using array methods on the tuple anywhere, you'll lose that functionality.

Option 2, use a tuple constructor function:

function buildTuple<E extends Element>(el: E, attrs: PickAttributes<E>): [E,PickAttributes<E>] {
  return [el, attrs];
}
const test4: Tuple = buildTuple("Tag", { /* types will be narrowed correctly */});

// works with arrays of Tuples
const tuples: Tuple[] = [
  buildTuple("Tag", {hidden: false}),
  buildTuple("Container", {})
]
  • Pros: Does what you want and keeps the value as an array-tuple.
  • Cons: Adds a function call.

playground

chrisbajorin
  • 5,993
  • 3
  • 21
  • 34