Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[material-ui][docs] Document the correct way to extend a component #19461

Open
JesalR opened this issue Jan 29, 2020 · 9 comments
Open

[material-ui][docs] Document the correct way to extend a component #19461

JesalR opened this issue Jan 29, 2020 · 9 comments
Labels
docs Improvements or additions to the documentation typescript

Comments

@JesalR
Copy link

JesalR commented Jan 29, 2020

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: #17454

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

@embeddedt
Copy link
Contributor

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

@JesalR
Copy link
Author

JesalR commented Jan 29, 2020

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

@ianschmitz
Copy link
Contributor

Related: #17491.

@strothj
Copy link

strothj commented Feb 3, 2020

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>;

@fyodor-e
Copy link
Contributor

fyodor-e commented Feb 12, 2020

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.

@jayt08
Copy link

jayt08 commented Nov 19, 2020

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>;

@strothj Thanks for this solution. But how i can overwrite prop type? Lets say, i want to do something like this:

import {Button as MUIButton, ButtonProps, ButtonTypeMap} from '@material-ui/core';
import * as React from 'react';

export const Button = <
  D extends React.ElementType = ButtonTypeMap['defaultComponent'],
  P = {}
>(props: { color?: string } & Omit<ButtonProps<D, P>, 'color'>) => {
  const {color, ...restProps} = props;

  return (
    <MUIButton {...restProps} />
  );
};

Its throw an error

Property 'component' does not exist on type 'IntrinsicAttributes & { color?: string; } & Pick<DefaultComponentProps<ExtendButtonBaseTypeMap<ButtonTypeMap<{}, "button">>>, "form" | ... 285 more ... | "value">'. TS2322

@oliviertassinari oliviertassinari added the docs Improvements or additions to the documentation label Apr 17, 2021
@douglaszaltron
Copy link

and forwardRef?

@marcelarie
Copy link

So how is the state of this in 2021 ?

I am getting the following error:

1. typescript: Property 'text' does not exist on type 'ExtendButtonBaseTypeMap<{ props: { children?: ReactNode; classes?: Partial<ButtonClasses> | undefined; color?: "inherit" | "primary" | "secondary" | "success" | "error" | "info" | "warning" | undefined; ... 9 more ...; variant?: "text" | ... 2 more ... | undefined; }; defaultComponent: "button"; }> & { ...; }'.

2. typescript: Property 'variant' does not exist on type 'ExtendButtonBaseTypeMap<{ props: { children?: ReactNode; classes?: Partial<ButtonClasses> | undefined; color?: "inherit" | "primary" | "secondary" | "success" | "error" | "info" | "warning" | undefined; ... 9 more ...; variant?: "text" | ... 2 more ... | undefined; }; defaultComponent: "button"; }> & { ...; }'.

with the following code:

import { Button as MuiButton, ButtonProps } from "@mui/material";
import { FC } from "react";

const Button: FC<ButtonProps> = ({ text, variant }) => {
  return (
    <MuiButton variant={variant} color="inherit">
      {text}
    </MuiButton>
  );
};

export default Button;

@vishalraj82
Copy link

vishalraj82 commented Jul 3, 2022

import { Button as MuiButton, ButtonProps } from "@mui/material";
import { FC } from "react";

// Add your custom props here
type MyButtonProps = {
    text: string
}

const Button: FC<ButtonProps & MyButtonProps > = ({ text, variant }) => {
  return (
    <MuiButton variant={variant} color="inherit">
      {text}
    </MuiButton>
  );
};

export default Button;

@mapache-salvaje mapache-salvaje changed the title The correct way to extend a Material UI component [material-ui][docs] Document the correct way to extend a component Nov 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Improvements or additions to the documentation typescript
Projects
None yet
Development

No branches or pull requests

10 participants