Home > Net >  Typescript Conditionally required type using recursion
Typescript Conditionally required type using recursion

Time:02-04

I've been struggling with this one all day, I'm new to typescript and trying to conditionally require an object property when a particular value is present in an array.

Here is link generator. Only Pinterest cares about an image URL, so I would like to require it if 'pinterest' is within the array of platforms.

type share = 'facebook' | 'pinterest' | 'twitter'

interface shareProps {
    pageTitle: string;
    platforms: share[];
    url: string;
    description: string;
}

interface propsWithImage extends shareProps {
    image: string;
}

type hasPinterest<T extends any[]> = T extends [infer U, ...infer V]
    ? U extends 'pinterest'
        ? propsWithImage
        : hasPinterest<V>
    : shareProps

function fn <T extends shareProps> (props: hasPinterest<T['platforms']>): string[] {
    const { platforms, url, description } = props
    const shareLinks: string[] = []
    platforms.forEach((platform) => {
        if (platform === 'pinterest') {
            shareLinks.push(`https://pinterest.com/pin/create/button/?url=${props.url}&media=${props.image}&description=${props.description}`)
        }
        if (platform === 'facebook') {
            shareLinks.push(`https://www.facebook.com/sharer/sharer.php?u=${props.url}`)
        }
        if (platform === 'twitter') {
            shareLinks.push(`https://twitter.com/intent/tweet?text=${props.description}`)
        }
    })
    return shareLinks
}

let shouldRequireImage = fn({
    pageTitle: 'Cool page',
    platforms: ['facebook', 'pinterest'],
    url: 'http://www.example.org',
    description: 'A really cool page',
    image: 'AwesomeImage.jpg'
})

let shouldNotRequireImage = fn({
    pageTitle: 'Cool page',
    platforms: ['facebook', 'twitter'],
    url: 'http://www.example.org',
    description: 'A really cool page',
})

console.log(shouldRequireImage)
console.log(shouldNotRequireImage)

hasPinterest appears to always be giving the type of shareProps when using the generic with 'platform' index. What am I missing?

TS Playground

CodePudding user response:

No recursion is necessary here. The trick is just extracting the value of platforms and determining--in TypeScript--whether it contains pinterest. To do that I use a handy little utility from this post:

type NeededUnionType<T extends any[]> = T[number];

That lets us convert the platforms array to a union and then we just determine if pinterest extends that union.

type HasPinterest<T extends Share[]> = 'pinterest' extends NeededUnionType<T> ? SharePropsWithImage<T> : SharePropsWithoutImage<T>

Then we just need to define your props types in a way that allows easily extracting the precise type of platforms using a generic type, extract that, and pass it to the helper function.

Here's the full solution:

type Share = 'facebook' | 'pinterest' | 'twitter'

type SharePropsWithoutImage<T extends Share[]> = 
{
    pageTitle: string;
    platforms: T;
    url: string;
    description: string;
  } 

type SharePropsWithImage<T extends Share[]> = 
{ 
    pageTitle: string;
    platforms: T;
    url: string;
    description: string;
    image: string;
  }

type ShareProps<T extends Share[]> = SharePropsWithImage<T> | SharePropsWithoutImage<T>

type NeededUnionType<T extends any[]> = T[number];

type HasPinterest<T extends Share[]> = 'pinterest' extends NeededUnionType<T> ? SharePropsWithImage<T> : SharePropsWithoutImage<T>

function fn <T extends Share[]> (props:HasPinterest<T>): string[] {
    // the code...
}

With a playground.

Let me know if you need any more explanation.

  •  Tags:  
  • Related