Material-ui: The correct way to extend a Material UI component

Created on 29 Jan 2020  路  5Comments  路  Source: mui-org/material-ui

We are using Material-UI as the base of a project, but are running in to issues extending components

import { Link, LinkProps } from "@material-ui/core";

export const HnLink = Link;

Works fine

import { Link, LinkProps } from "@material-ui/core";

export const HnLink: React.FC<LinkProps> = props => {
  // Some extra functionality
  return <Link {...props} />
}

Does not accept component and to props, resulting in TypeScript errors.

Type '{ to: string; component: typeof Link; }' is not assignable to type 'IntrinsicAttributes & AnchorHTMLAttributes<HTMLAnchorElement> & Pick<OverrideProps<TypographyTypeMap<{}, "span">, "span">, "ref" | ... 262 more ... | "variantMapping"> & { ...; } & CommonProps<...> & Pick<...> & { ...; }'.
  Property 'to' does not exist on type 'IntrinsicAttributes & AnchorHTMLAttributes<HTMLAnchorElement> & Pick<OverrideProps<TypographyTypeMap<{}, "span">, "span">, "ref" | ... 262 more ... | "variantMapping"> & { ...; } & CommonProps<...> & Pick<...> & { ...; }'.ts(2322)

The same is true of Tabs, wherein wrapping Tabs in our own component causes onChange to not be accepted

Type '(event: ChangeEvent<{}>, value: number) => void' is not assignable to type '((event: ChangeEvent<{}>, value: any) => void) & ((event: FormEvent<HTMLButtonElement>) => void)'.
  Type '(event: ChangeEvent<{}>, value: number) => void' is not assignable to type '(event: FormEvent<HTMLButtonElement>) => void'.ts(2322)

As also seen here: https://github.com/mui-org/material-ui/issues/17454

Is there a 'correct' way to wrap a Material-UI component that can alleviate typing issues?

typescript

Most helpful comment

Until that issue is resolved, you can pass along the generic parameters:

import Link, { LinkProps, LinkTypeMap } from "@material-ui/core/Link";
import React from "react";

export const HnLink = <
  D extends React.ElementType = LinkTypeMap["defaultComponent"],
  P = {}
>(
  props: LinkProps<D, P>,
) => {
  return <Link {...props} />;
};

export const test = <HnLink to="/test" />;

You can retrieve them by viewing the type of LinkProps:

export type LinkProps<
  D extends React.ElementType = LinkTypeMap['defaultComponent'],
  P = {}
> = OverrideProps<LinkTypeMap<P, D>, D>;

All 5 comments

Can you try React.ComponentProps<typeof Link> instead of LinkProps?

@embeddedt That causes the same typescript issue
https://codesandbox.io/s/react-typescript-vir6c

Until that issue is resolved, you can pass along the generic parameters:

import Link, { LinkProps, LinkTypeMap } from "@material-ui/core/Link";
import React from "react";

export const HnLink = <
  D extends React.ElementType = LinkTypeMap["defaultComponent"],
  P = {}
>(
  props: LinkProps<D, P>,
) => {
  return <Link {...props} />;
};

export const test = <HnLink to="/test" />;

You can retrieve them by viewing the type of LinkProps:

export type LinkProps<
  D extends React.ElementType = LinkTypeMap['defaultComponent'],
  P = {}
> = OverrideProps<LinkTypeMap<P, D>, D>;

The shorter example of implementing HnLink can be found in the documentation.

The problem with implementing LinkProps so it can be used without generic component is that TS has difficulties inferring a type from an object property type.

Say we have following fictional type

type T1<C> = {
  prop: C;
} & C;

Then we want to use it like

const t: T1 = { prop: { a: 'aaa' }}

We expect TS to infer the generic type C from usage. In our sample C should become { a: 'aaa' }. But TS complains that T1 should have one type argument.

So I don't see a way to implement LInkProps in a way that it infer component prop type from it usage. The only solution I see is described in TS guide I've provided above.

Was this page helpful?
0 / 5 - 0 ratings