Home > database >  Conditional type based on field of the same type
Conditional type based on field of the same type

Time:04-29

I have a function that generates an array of objects called nodes:

type Node = {
  type: 'text' | 'entity';
  // A node of type 'entity' for sure has props defined while a 'text' node does not have them.
  // I also require those nodes to be in the same array when returned from the buildNodes function
  props?: EntitySpecificProps;
}

type EntitySpecificProps = {
  id: string;
}


function buildNodes(): Node[] {
 ...
 return nodes;
}

Now let's say I generate some nodes and pass them to another function which is defined as:

function useEntityNode(props: EntitySpecificProps){
   ...
}

const nodes = buildNodes();
// typescript gives error because node props may be possibly undefined (given the optional type of 'props').
nodes.map((node) => node.type === 'text' ? node : useEntityNode(node.props))

How can I constrain node props to be defined if the type field of a node is of type entity?

CodePudding user response:

You can make this work by removing the Optional "?" from props when type is "entity" by using this NodeUnion mapping transformation.

NodeUnion makes sure that if type = "text" we get { type: "text", props ?: T }

If type = "entity" we get { type: "entity", props: T } ie Props field becomes required

export type Node = {
  type: 'text' | 'entity';
  props?: EntitySpecificProps;
}

/*  This makes sure that if type = 'text' we get { type: 'text', props ?: T }
 if type = 'entity' we get { type: 'entity', props: T } ie Props field becomes required
 */

export type NodeUnion = 
  | Node["type"] extends infer T 
    ? T extends any  
      ? T extends 'entity' 
        ? Required<Node> & { type: T }
        : Node & { type: T }
      : never 
    : never

export type EntitySpecificProps = {
  id: string;
}

declare function buildNodes(): NodeUnion[] 

declare function useEntityNode(props: EntitySpecificProps): void

const nodes = buildNodes();

nodes.map((node) => node.type === 'text' ? node : useEntityNode(node.props))

Code Playground

CodePudding user response:

You could specify all possible Node sub-types in their separate type. The types can then be discriminated by their type property.

type _Text = {
  type: 'text'
}

type Entity = {
  type: 'entity'
  props: EntitySpecificProps
}

type EntitySpecificProps = {
  id: string;
}

type _Nodes = _Text | Entity

function buildNodes(): _Nodes[] {
 return [];
}

function useEntityNode(props: EntitySpecificProps){}

const nodes = buildNodes();
nodes.map((node) => node.type === 'text' ? node : useEntityNode(node.props))

Note: I prefixed some types here with an _ because the names Text and Node collide with existing types.

  • Related