Typescript - Conditional Type / Optional

I have a component, It takes arguments such as :

interface Props {
  label: string;
  children?: React.ReactNode;
  withoutActions?: boolean;
  fieldKey?: KeyProperties;
  corporate: Corporate;
}

The withoutActions and fieldKey are used by a form. If the withoutActions prop is true, then there should not be a provided fieldKey.

However, if my withoutActions is undefined, then i should enforce my fieldLabel to be of type **KeyProperties** which is a specific list of available values for it.

If withoutActions is undefined, the other SHOULD NOT be defined at all If withoutActions is true, the other SHOULD be defined respecting the specific keyProperties type.

How can i implement that ?


Similar to the other answer, but you can refactor it a bit to be cleaner. You did not specify what the expected behavior is if withoutActions is false instead of true or undefined. Here, I assume the behavior for false and undefined are the same. If false is not a valid type, you could just swap withoutActions?: false for withoutActions: undefined like the other answer.

type Props = {
  label: string;
  children?: React.ReactNode;
  corporate: Corporate;
} & (
  {
    withoutActions: true;
  } |
  {
    withoutActions?: false;
    fieldKey: KeyProperties;
  }
)

However, there is an important pitfall here that you should be aware of. Because of TypeScript's structural typing, you only get excess property checking when you are directly assigning an object literal. You do not get excess property checking when you assign an an object as an inferred type. TypeScript and React treat direct props declarations as if they are object literals, and will do excess property checking like you seem to desire. However in some cases, if you assign objects to variables and let their type be inferred, TypeScript may not warn that there is an excess property present.

Check out this demo based on your original example. Example #1 and #2 will error because of excess property checking, but example #3 will not.

const ExampleOne = () => {
  // Type error - excess property checking
  return <Component label={''} corporate={''} withoutActions fieldKey={''} />;
}

const ExampleTwo = () => {
  const props: Props = {
    label: '',
    corporate: '',
    withoutActions: true,
  // Type error - excess property checking
    fieldKey: '',
  }

  return <Component {...props} />;
}

const ExampleThree = () => {
  const props = {
    label: '',
    corporate: '',
    withoutActions: true,
    fieldKey: '',
  }

  // No type error - no excess property checking
  return <Component {...props} />;
}

I did not fully understand your requirement, but I would use a type alias instead of an interface. E.g. something like this:

  type Props = {
    label: string,
    children?: React.ReactNode,
    withoutActions: true,
    corporate: Corporate
  } | {
    label: string,
    children?: React.ReactNode,
    withoutActions: undefined,
    fieldKey: KeyProperties,
    corporate: Corporate
  }