Lucas Caton
Lucas Caton

Reputation: 3255

How to pass dynamic props to a React component that accepts a union of objects as its type?

I want a component to forward dynamic props (I'm using spread operator) and this is the code I have at the moment:

type Props = {
  identifier: "YouTube";
  attributes: { id: string };
} | {
  identifier: "Figure";
  attributes: { src: string };
};

const YouTube = ({ id }) => <p>{id}</p>;
const Figure = ({ src }) => <p>{src}</p>;

const Shortcode = ({ identifier, attributes }: Props): JSX.Element => {
  switch (identifier) {
    case "YouTube":
      return <YouTube {...attributes} />;
    case "Figure":
      return <Figure {...attributes} />;
  }
};

TypeScript is complaining about this:

return <YouTube {...attributes} />;
// Type '{ id: string; } | { src: string; }' is not assignable to type 'IntrinsicAttributes & { id: any; }'.
//   Property 'id' is missing in type '{ src: string; }' but required in type '{ id: any; }'.ts(2322)

return <Figure {...attributes} />;
// Type '{ id: string; } | { src: string; }' is not assignable to type 'IntrinsicAttributes & { src: any; }'.
//   Property 'src' is missing in type '{ id: string; }' but required in type '{ src: any; }'.ts(2322)

If I explicitly pass the props, TypeScript behaves as expected:

<YouTube id="foo" />      // TS says this is ok
<YouTube wrong="props" /> // TS says this is missing the `id` prop

However, I need it to accept dynamic props. Any thoughts on how to solve this? Thanks.

Upvotes: 3

Views: 919

Answers (1)

This is because of destructuring. TS does not treat them as a part of one union.

In order to make TS happy, just don't use destructuring:

import React, { FC } from 'react';


type Props = {
    identifier: "YouTube";
    attributes: { id: string };
} | {
    identifier: "Figure";
    attributes: { src: string };
};

const YouTube: FC<{ id: string }> = ({ id }) => <p>{id}</p>;
const Figure: FC<{ src: string }> = ({ src }) => <p>{src}</p>;

const Shortcode = (props: Props): JSX.Element => {
    switch (props.identifier) {
        case "YouTube":
            return <YouTube {...props.attributes} />; // ok
        case "Figure":
            return <Figure {...props.attributes} />; // ok
    }
};

Upvotes: 2

Related Questions