Jacob
Jacob

Reputation: 458

TypeScript conditional types for props

I have a component that can be in two different states, editing mode and viewing mode. To accommodate for that I have a union type to make this work, but I still get TypeScript complaining in the args of my component:

type View = {
    isEditing: false;
    viewHandler: () => void;
    someOtherProp: any // this is unique to view mode
};

type Edit = {
    isEditing: true;
    editHandler: (id: string) => void;
};

export type MyProps = View | Edit;

It is then complaining here:

const Item: React.FC<MyProps> = ({
    isEditing = false,
    editHandler,
    ~~~~~~~~~~~
}) => {

Property 'editHandler' does not exist on type 'PropsWithChildren<MyProps>'.

What am I missing?

Upvotes: 13

Views: 13167

Answers (3)

Carl-Robert
Carl-Robert

Reputation: 162

interface ViewProps {
  isEditing?: false;
  viewHandler: () => void;
  someOtherProp: any;
}

interface EditProps {
  isEditing: true;
  editHandler: (id: string) => void;
  viewHandler?: never;
}

const Item = (props: ViewProps | EditProps) => {
  if (props.isEditing) {
    return <EditItem {...props} />;
  }
  return <ViewItem {...props} />;
};

const ViewItem = (props: ViewProps) => <div>View</div>;

const EditItem = (props: EditProps) => <div>Edit</div>;

Usage:

<Item viewHandler={() => {}} someOtherProp="" />;
<Item isEditing editHandler={() => {}} />;

All other combinations are not allowed.

Upvotes: 0

Jacob
Jacob

Reputation: 458

So the solution I used now is adding it and typing it as never:

type View = {
    isEditing: false;
    viewHandler: () => void;
    editHandler?: never;
};

type Edit = {
    isEditing: true;
    editHandler: (id: string) => void;
    viewHandler?: never;
};

export type MyProps = View | Edit;

And at the point where the event handler gets invoked or passed down, I use a ! to assure TypeScript that this cannot be undefined. Kudos to Nadia who pointed me in the right direction in a comment above!

Upvotes: 9

sakshya73
sakshya73

Reputation: 7152

I think it should also be present in your View type like this:

type View = {
  isEditing: false;
  editHandler?:(id: string) => void;
};

type Edit = {
  isEditing?: true;
  editHandler: (id: string) => void;
};

Upvotes: 2

Related Questions