How does Material-UI Button component infer the props for the component passed to `component` prop?

Viewed 1297

Can anyone please explain how Material-UI extends the props of its Button component with the props of my component if I pass a specific component in the component prop?

interface MyLinkProps extends ButtonBaseProps {
  someRandomProp: string
}

const MyLink: React.FC<MyLinkProps> = () => {
  return <div></div>
}

<Button component={MyLink} someRandomProp="random">Something</Button>

As in this case, the Button component is now aware of the someRandomProp prop that belongs to my component; which is being passed to component prop on the Button component.

I would like to achieve the same effect. I have an Image component which has a prop component which I would like to infer the props of the component that is being passed.

For example, if there is something like:

<MyImage component={NextImage} {...propsOfNextImage} />

Basically, I would like MyImage to auto-detect and extend the props of NextImage.

1 Answers

You can see the type definition of OverridableComponent here, this one is responsible for merging the props of the overridable component with the original props of the component.

For reference, see how it's used in a MUI component here. The first generic type parameter is a type with the following properties:

  • props: The original props of your component before being merged with the props of the overridable component.
  • defaultComponent: The default root component if you don't provide any.
import { OverridableComponent } from '@mui/material/OverridableComponent';

interface MyImageProps {
  src: string;
  myCustomProps?: string;
}
interface MyImageTypeMap {
  props: MyImageProps;
  defaultComponent: 'img';
}

const MyImage: OverridableComponent<MyImageTypeMap> = (props) => {
  const RootComponent = props.component || 'img';
  return (
    <RootComponent src={props.src} {...props}>
      {props.children}
    </RootComponent>
  );
};

Usage

{/* normal component with MyImageProps. The root component is an img element */}
<MyImage src={src} />
{/* component with MyImageProps & ButtonProps. The root component is a Button*/}
<MyImage src={src} component={Button} variant="contained">
  I am actually a button in disguise
</MyImage>

Live Demo

Codesandbox Demo

Related