stahlhammer
stahlhammer

Reputation: 107

Make a property required based on another property of a generic interface

I have an interface for a react component. There are a bunch of properties there, but I want one property to affect the others. So, what I have is:

export interface IMyAwesomeComponentProps<T = {}> {
    className: string
    defaultPath?: ISomeOtherInterface<T>

    // more properties...
    // ...and finally

    isDraggable?: boolean
    onDrag?: () => {}
}

This thing is implemented like so:

export class MyAwesomeComponent<T> extends React.Component<IMyAwesomeComponentProps<T>> {
   //...
}

So, what I what to do is when I pass the "isDraggable" as "true", I want the "onDrag" to be required.

To resolve this, I first excluded "onDrag" function from the interface and made a new type. Then I made a utility type which basically says "if that "isDraggable" thing is true, then return the interface combined with "TDraggableProps" otherwise just give me the interface back". Overall it looks like this:

type TDraggableProps = {  
  onDrag: () => void
  // maybe some more props  
}

type Test<I extends ITreeProps> = I['isDraggable'] extends true ? I & TDraggableProps : I

export class MyAwesomeComponent<T> extends React.Component<Test<IMyAwesomeComponentProps<T>>>

Aaaand, this kinda works except it's not. And I have no idea what to do with that generic that comes into the interface.

Upvotes: 0

Views: 663

Answers (3)

rzwnahmd
rzwnahmd

Reputation: 1082

I would make the Props a type then move all interface for all the common properties and for different properties, I think this approach looks clean and readable:

interface Common {
  x: number;
  y: number;
  z: number;
}

interface RequiredB {
  a: true;
  b: () => {}
}

interface OptionalB {
  a: false;
  b?: () => {}
}

type Props = Common & (RequiredB | OptionalB);

const obj: Props = {
  x: 1,
  y: 2,
  z: 3,
  //a: false, // b is not required now
  //a: true, // b is required now
}

Upvotes: 1

Jerome
Jerome

Reputation: 2761

I would change your interface IMyAwesomeComponentProps signature by creating a IDraggable interface and include it in your IMyAwesomeComponentProps. Of course assuming here that having the situation where isDraggable = false and the onDrag is defined is useless...

Something like this :

    interface ITest<T = {}> {
    a: string;
    b: IInner<T>;
    isDraggable?: IIsDragble;
}

interface IIsDragble {
    onDrag: () => void;
}

const a: ITest = {
    a: "dsd",
    b: {
        valInner: "ssdds",
    },
};

const b: ITest = {
    a: "dsd",
    b: {
        valInner: "ssdds",
    },
    isDraggable: {
        onDrag: () => true,
    },
};

Upvotes: 1

Shlang
Shlang

Reputation: 3230

You can use Discriminated union

type Props = {
  isDraggable: true;
  onDrag: () => void;
} | {
  isDraggable: false;
}

const Component = (props: Props) => {}

Component({ isDraggable: false }); // Ok
Component({ isDraggable: true }); // Error onDrag is missing
Component({ isDraggable: true, onDrag: () => {} }); // Ok

If you need to add some common props you can either make the types as interface and extend some base interface, or just extract them to a separate type and join to your props

type Props = {
  isDraggable: true;
  onDrag: () => void;
} | {
  isDraggable: false;
}

type StaticProps = {
  className: string;
}

const Component = (props: Props & StaticProps) => {}

Component({ isDraggable: false, className: "123" });
Component({ isDraggable: true, className: "123" }); // Error onDrag is missing
Component({ isDraggable: true, onDrag: () => {}, className: "123" }); // Ok

Upvotes: 1

Related Questions