Search code examples
reactjstypescriptinterfacedynamic-components

Conditional props in intreface


I have a question regarding interfaces:

Basically I'm creating a dynamic component that change depending on which page it is. My interface is constructed like so:

interface Props {
heading: string;
description: string;
signUp?: boolean;
signUpButton1?: string;
signUpButtonLink1?: string;
signUpButton2?: string;
signUpButtonLink2?: string;
startFreeAccount?: boolean;
startFreeAccountSignUpButton?: string;
startFreeAccountLink1?: string;
startFreeAccountSignUpLink1?: string;
startFreeAccountSignUpLink2?: string;
startFreeAccountSignUpLink3?: string;
contactUsForDemo?: boolean;
contactUsForDemoInput1?: string;
contactUsForDemoInput2?: string;
contactUsForDemoInput3?: string;
contactUsForDemoButtonText?: string;
ThanksForDemo?: boolean;
ThanksForDemoButton?: string;
} 

In my code I then use for example:

{props.signUp && 
          <div className="mt-10 flex flex-col  items-center gap-x-6">
            <Link
              href={"/signUpPractitioner/startFreeAccount"}
              className=" bg-black mb-4 -ml-[14vw] px-16 py-1.5 text-base font-semibold leading-7 text-white shadow-sm  outline outline-offset-0 outline-black"
            >
              {props.signUpButton1}
              
            </Link>
            <Link href={"/practitioner/contactUsForDemo"} 
            className=" bg-white -ml-[14vw] px-10 py-1.5 text-base font-semibold leading-7 text-black outline outline-offset-0 outline-black     shadow-sm"
>
               {props.signUpButton2} <span aria-hidden="true">→</span>
            </Link>
          </div> }

Setting "signUp == true" would display the information in the above part of the component. My issue is that Link (from NEXTJS) cannot handle the fact that "signUpButtonLink1" might be undefined. If I put that "signUpButtonLink1" has to have a value, then even if I don't use the "signUp" part of the component, I still need to assign some value to "signUpButtonLink1" which obviously doesnt make sense (it makes sense in terms of how the current interface is constructed but not in terms of functionality).

My question is: Is it possible to set that "signUpButton1, signUpLink1 etc etc" only needs to be set if "signUp === true" ? It the same for all bools in the interface and the props "affiliated" with it.

Thanks for your help!


Solution

  • You can do this with simple unions

    type Props = {
      heading: string;
      description: string;
    } & (
      | {
          signUp?: false
          signUpButton1?: never
          // ...
        }
      | {
          signUp: true
          signUpButton1: string
          // ...
        }
    ) & (
      | {
        startFreeAccount?: false;
        startFreeAccountSignUpButton?: never;
        // ...
      }
      | {
        startFreeAccount: true;
        startFreeAccountSignUpButton: string;
        // ...
      }
    ) & (
      ...
    )
    

    Maybe add a utility type:

    type OptionalProps<K extends string, T> = 
      | { [key in K]?: false } & { [key in keyof T]?: never }
      | { [key in K]: true } & T
    
    type Props = {
      heading: string
      description: string
    }
      & OptionalProps<'signUp', {
        signUpButton1: string,
      }>
      & OptionalProps<'startFreeAccount', {
        startFreeAccountSignUpButton: string
      }>
      & ...
    

    The properties with ?: never are not necessary, but they will make your life easier if you destructure props into individual variables. Without ?: never you won't be able to do it, it will tell that property with name ... doesn't exist in the object.

    Or you can just use objects instead of inline props:

    type Props = {
      heading: string
      description: string
      signUp?: {
        button1: string
        button2: string
        // ...
      }
      startFreeAccount?: {
        signUpButton: string
        // ...
      }
      // ...
    }
    

    I would even argue that these objects is easier to read and use