From docs:
https://next.material-ui.com/components/buttons/
The ButtonBase component provides a property to handle this use case: component
https://next.material-ui.com/api/button-base/
component | element type | 'button' | The component used for the root node. Either a string to use a DOM element or a component.
With MUI v4
const MyButton = () => <ButtonBase component="span" />;
Property 'component' does not exist on type 'IntrinsicAttributes & { action?: ((actions: ButtonBaseActions) => void) | undefined; buttonRef?: ((instance: unknown) => void) | RefObject
| null | undefined; centerRipple?: boolean | undefined; ... 6 more ...; TouchRippleProps?: Partial<...> | undefined; } & CommonProps<...> & Pick<...>'.ts(2322)
component
property should be accepted
component
property is not accepted
Add component
property to a ButtonBase
component
Need for integration with a third-party routing library
| Tech | Version |
|--------------|---------|
| Material-UI | v4.0.0 |
| React | v16.8.6 |
| Browser | Chrome |
| TypeScript | v3.4.5 |
While the error message isn't helpful I fail to see why const MyButton = () => <ButtonBase component="lol" />;
should be accepted by the type-checker. It will trigger a warning in React which is why we want to prevent it at the type level.
Please include code that should be accepted in your opinion.
I guess that @Slessi used lol
only as an example.
The problem is that there is no way to pass component
prop to Button.
Just try to compile TS code from this example: https://next.material-ui.com/components/buttons/#third-party-routing-library
I guess that @Slessi used
lol
only as an example.
thank you @piotros :) i thought this was obvious
Am I right that the problem occurs because of lack of OverridableComponent
in Button.d.ts
typings?
I guess that @Slessi used
lol
only as an example.thank you @piotros :) i thought this was obvious
Which is why I ask for a specific example. We're have no issue with the example. Our docs are type-checked as well.
Just try to compile TS code from this example: next.material-ui.com/components/buttons/#third-party-routing-library
Am I right to assume that this example is not working with TypeScript 3.4.5?
To add to this, the following doesn't compile in TypeScript in MUIv4, but should:
// MyButton.ts
import { Button } from '@material-ui/core'
import { ButtonProps } from '@material-ui/core/Button'
import React from 'react'
export const MyButton = React.forwardRef<HTMLButtonElement, ButtonProps>((btnProps, ref) => (
<Button
ref={ref}
variant="contained"
color="primary"
{...btnProps}
/>
))
// MyPage.ts
function MyPage() {
return (
<MyButton component={Link}> // <-- error on this
Click me
</MyButton>
)
}
error message is:
Type '{ children: string; component: FunctionComponent<{}>; }' is not assignable to type 'IntrinsicAttributes & Pick<{ action?: ((actions: ButtonBaseActions) => void) | undefined; buttonRef?: ((instance: unknown) => void) | RefObject
| null | undefined; centerRipple?: boolean | undefined; ... 6 more ...; TouchRippleProps?: Partial<...> | undefined; } & { ...; } & CommonProps<...> & Pick<...>, "c...'.
Property 'component' does not exist on type 'IntrinsicAttributes & Pick<{ action?: ((actions: ButtonBaseActions) => void) | undefined; buttonRef?: ((instance: unknown) => void) | RefObject| null | undefined; centerRipple?: boolean | undefined; ... 6 more ...; TouchRippleProps?: Partial<...> | undefined; } & { ...; } & CommonProps<...> & Pick<...>, "c...'.
I'm using TypeScript 3.3.3, but also checked 3.4.5, and getting the same error
@bengry I don't think we can support this particular pattern while also supporting the generic types from the component
prop. I would recommend using the props
key in the theme
to override default props: https://material-ui.com/customization/globals/#default-props
Just try to compile TS code from this example: next.material-ui.com/components/buttons/#third-party-routing-library
A fresh clone from this example (edit in codesandbox, download zip, unzip, yarn, yarn add typescript, yarn start) starts without issues.
Without the code and tsconfig I'm not able to debug this issue.
@eps1lon Take a look at https://github.com/bengry/material-ui-v4-button-component-ts-repro, and more specifically: https://github.com/bengry/material-ui-v4-button-component-ts-repro/blob/master/src/App.tsx#L9
Due to ComponentWorkaroundProps
the types work fine. Without them TypeScript complains about component
prop not being declared on MyButton
.
I don't see a reason why ButtonProps
can't include what's introduced in ComponentWorkaroundProps
inside the library. Am I missing something?
There's a lot of types implementation details that goes into this. Generic props are currently impossible to get right. ButtonProps
cannot include component
which is working fine with just <Button component={Link} />
due to this limitation.
However, you don't have to create that additional component wrapper to begin with. As I said you could provide a theme to your Buttons that uses contained
as the default variant
.
This would avoid all the type workarounds. Your MyButton
is currently not sound. If I pass an achor as the component the type in the ref would be incorrect. It's one of the reasons why our Button is typed the way it is. To fix issues with generic parts of the props (ref, events etc). It's probably best if you just cast your MyButton
to our Button
e.g. const MyButton = React.forwardRef(...) as Button
.
I agree the typing for this is hard to get right, but using a theme is not an option in this case, since our case is a bit more complex - I simplified it for the example, but basically - we pass some default props to Button
and export a variant of it, only some of the components use, and only sometimes.
Is there a better way to create a variant of an existing MUI component that injects some props to it?
but using a theme is not an option in this case, since our case is a bit more complex - I simplified it for the example, but basically
I need the actual example to help you properly. Otherwise it's just guesswork what might help you.
@eps1lon kk right u r, my example actually is fine, my problem lies with ButtonBaseProps
const AdapterLink = React.forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => (
<Link innerRef={ref as any} {...props} />
));
function MyButton(props: ButtonBaseProps) {
return <ButtonBase {...props} />;
}
function ButtonRouter() {
return (
<MyButton color="primary" component={AdapterLink} to="/">
Simple case
</MyButton>
);
}
Property 'component' does not exist on type 'IntrinsicAttributes & { action?: ((actions: ButtonBaseActions) => void) | ...
@Slessi Looks like the same issue as @bengry. You don't need MyButton
though. Would help if you include a concrete example.
@eps1lon Come on you're just nitpicking now, in my particular case I want to share a styled button. There could be many reasons to have a shared ButtonBase
component
const useStyles = makeStyles((theme: Theme) => ({
fancy: {
// Magical JSS goes here
},
}));
function MyFancyButton(props: ButtonBaseProps) {
const classes = useStyles();
return <ButtonBase className={classes.fancy} {...props} />;
}
There could be many reasons to have a shared ButtonBase component
Probably. There are also many reasons to not use a wrapper components. I can't recommend an implementation without the full picture. I'm just trying to avoid over-abstracting things. And if I don't know what you're doing I can't explain to you why this is happening.
In this particular case wrapper components are quite hard to type right because of the generic nature of our components. This is why I need concrete examples so that I can evaluate if another approach might be better.
This is quite common in open source repositories. You will rarely see maintainers working on generic issues.
Hope this justification is sufficient for you and we can finally work on the issue.
I don't have a good answer for you yet. TypeScript has quite a few limitations currently in the JSX domain. If your wrappers don't have any additional props it's probably best to just cast them back to the Material-UI button e.g. export default MyFancyButton as ButtonBase
.
@eps1lon if they do have additional props?
@eps1lon here's a more realistic example:
const CancelButton = React.forwardRef<
HTMLButtonElement,
Omit<ButtonProps, 'component'> & ComponentWorkaroundProps<LinkProps<void>>
>(({ children, ...props }, ref) => {
return (
<Button variant="contained" {...props} ref={ref} color="secondary">
{children || 'Cancel'}
</Button>
);
});
const ConfirmButton = React.forwardRef<
HTMLButtonElement,
Omit<ButtonProps, 'component'> & ComponentWorkaroundProps<LinkProps<void>>
>(({ children, ...props }, ref) => {
return (
<Button variant="contained" {...props} ref={ref} color="primary">
{children || 'Confirm'}
</Button>
);
});
These two buttons should be used throughout the app in cases where you want a Confirm or a Cancel button (think inside Dialogs for example, but could go inside other components as well). However, sometimes you want these to be links (using the Link
from @reach/router
or react-router
), but still have the same props passes, so you'd like to do:
<CancelButton component={Link} to="/" />
In this case the cancel button does a navigation back to the home page.
This does not currently compile in MUI v4, due to the aforementioned issue in this thread.
Just want to chime in and am having this same issue. Trying to get our codebase migrated to v4 and this issue has been a real headache!
I created a stackoverflow issue regarding it with a code sandbox and such. My use case is basically identical to @bengry's. We have various wrapped buttons for things like Cancel, Confirm, etc with some custom styling / props and what not.
Here's the stackoverflow if interested: https://stackoverflow.com/questions/56300136/when-extending-button-component-property-not-found-what-is-the-correct-way-to
Would love to get past this issue! Thanks for all the awesome work on v4!
~Closing this in favor of #15695. For issues related to invalid hooks call please open a separate issues. This issue was about typescript related issues.~ Wrong tab
Use React.forwardRef<T, Omit<LinkProps, "forwardedRef">>
instead of React.forwardRef<T, LinkProps>
solves the problem.
Example codes:
import { Button } from "@material-ui/core";
import React from "react";
interface LinkProps {
forwardedRef: React.Ref<HTMLAnchorElement>;
href?: string;
onClick?: VoidFunction;
}
const Link: React.FunctionComponent<LinkProps> = ({ forwardedRef }) => {
return <a ref={forwardedRef} />;
};
const ForwardedLink = React.forwardRef<
HTMLAnchorElement,
Omit<LinkProps, "forwardedRef">
>((props, ref) => <Link forwardedRef={ref} {...props} />);
const AnotherComponent: React.FunctionComponent<{}> = () => {
return (
<div>
<Button
component={ForwardedLink}
href={"123"}
onClick={() => {
return;
}}
/>
<ForwardedLink />
</div>
);
};
I also have this problem. TypeScript complains that the "component" prop is missing on ButtonBase.
The docs state that it exists, whereas in this commit we can see it removed.
The docs state that it exists, whereas in this commit we can see it removed.
Because in that commit it was added at another location.
Using:
interface IButtonBaseProps extends ButtonBaseProps {
component?: HTMLElement
}
In the interim.
@WillSquire How does that help? The button doesn't magically use that interface instead of its normal one.
No it doesn't, but because the exported components do have component
fields I can extend
the imported prop definition that's missing it in my case.
import Tab, { TabProps } from '@material-ui/core/Tab'
import React from 'react'
import { Link, LinkProps } from 'react-router-dom'
import { Omit } from '../../../util'
// Work around for issue: https://github.com/mui-org/material-ui/issues/15827
interface ITabProps extends TabProps {
component?: HTMLElement
}
export type ILinkTab = ITabProps & Omit<LinkProps, 'innerRef'>
const CollisionLink = React.forwardRef<
HTMLAnchorElement,
Omit<LinkProps, 'innerRef'>
>((props, ref) => <Link innerRef={ref} {...props} />)
export const LinkTab: React.FC<ILinkTab> = props => {
return <Tab component={CollisionLink} {...props} />
}
@WillSquire Your types would accept <LinkTab component={NotALink} />
. Is this actually expected?
If I may jump in here. The ButtonProps
type accepts two generics and this worked perfectly for me.
export type ButtonLinkProps = ButtonProps<typeof Link, LinkProps>;
export const ButtonLink = (props: ButtonLinkProps) => (
<Button {...props} component={Link} />
);
The pain points for me are with the other components that use ExtendButtonBase
instead of ExtendButtonBaseTypeMap
- IconButton
, Tab
, MenuItem
, etc.
I can also confirm changing the IconButton
type definitions to match fixes the typescript errors for me.
Modified IconButton
types:
import { IconButton as MuiIconButton, PropTypes } from '@material-ui/core';
import { ExtendButtonBase, ExtendButtonBaseTypeMap } from '@material-ui/core/ButtonBase';
import { IconButtonClassKey } from '@material-ui/core/IconButton';
import { OverrideProps } from '@material-ui/core/OverridableComponent';
import * as React from 'react';
type IconButtonTypeMap<P, D extends React.ElementType> = ExtendButtonBaseTypeMap<{
props: P & {
color?: PropTypes.Color;
disableFocusRipple?: boolean;
edge?: 'start' | 'end' | false;
size?: 'small' | 'medium';
};
defaultComponent: D;
classKey: IconButtonClassKey;
}>;
export const IconButton: ExtendButtonBase<IconButtonTypeMap<{}, 'button'>> = MuiIconButton;
export type IconButtonProps<D extends React.ElementType = 'button', P = {}> = OverrideProps<
IconButtonTypeMap<P, D>,
D
>;
Usage:
export const FacebookLink = (props: IconButtonProps<'a'>) => (
<IconButton
href="https://www.facebook.com/"
target="_blank"
rel="noopener noreferrer"
{...props}
>
<FacebookIcon color="inherit" fontSize="inherit" />
</IconButton>
);
FYI, I'm migrating from v3 right now, so I haven't tried running my app yet.
Implemented @CarsonF's solution in #16487!
Is this about to be fixed or is it even considered a bug? I just copy-pasted the code from the docs for my link component with minor changes and the TS error shows up:
Property 'component' does not exist on type 'IntrinsicAttributes...
import Button, { ButtonProps } from "@material-ui/core/Button";
import { withStyles, WithStyles } from "@material-ui/core/styles";
import { GatsbyLinkProps, Link } from "gatsby";
import * as React from "react";
import { styles } from "./ButtonLink.styles";
export type ButtonLinkProps = WithStyles<typeof styles> &
ButtonProps &
GatsbyLinkProps<{}> & {
partiallyActive?: boolean;
replace?: boolean;
to: string;
};
const LinkComponent = React.forwardRef<HTMLAnchorElement, GatsbyLinkProps<{}>>(
({ ref, innerRef, ...props }, linkRef) => (
<Link innerRef={linkRef as Function} {...props} />
),
);
class ButtonLink extends React.PureComponent<ButtonLinkProps> {
public render() {
const { classes, innerRef, ...props } = this.props;
return (
<Button
{/* ^^^^^^ Property 'component' does not exist on type 'IntrinsicAttributes... */}
component={LinkComponent}
activeClassName={classes.active}
{...props}>
{this.props.children}
</Button>
);
}
}
Just a quick summary why this is currently an issue and hasn't been fixed yet:
Usually the type of a component and the type of its props are closely related. Basically the type of the component is just something like (props: Props) => JSX.Element
. In this world higher-order components or wrapping the component by simply forwarding the same props type works without any issue. However, the base component would not accept excess props applied to the overwritten component
nor would it adjust event handler types to recognize the specific Element
i.e. polymorphism does not really work with just a props interface.
<Button component={RouterLink} to="/home" />; // didn't used to be type checked
<Button component="a" onClick={event => { /* event.currentTarget would either be implicitly any or HTMLButtonElement */}} />; //
In order to fix the last issues we need to overload the call signature of the function which means higher-order components won't work anymore. They usually infer a prop type which does not support polymorphism. Forwarding doesn't work either because again it uses a props type rather than a function type.
Even if we would revert the change to allow component
on decorated components <StyledButton component={RouterLink} to="/home" />
would still not work. But we are able to make <Button component={RouterLink} to="/home" />
completely type safe.
I'm currently exploring a different API that wouldn't accept a component type but a render prop. It would simplify typings (for humans and compilers) by a lot but comes with a bit manual wiring and doesn't look quite nice (if you care about it).
I would appreciate it if any further reports only include the actual code that produced the error and then only if that error got reported by a build tool. IDEs (especially Webstorm) have issues with overloaded call signatures of components as well which is another issue (and one we likely can't fix anyway).
If it can help this is how I fixed it: component={"div" as ElementType}
I know it's a hack but...
const MenuItemSelectAll: React.SFC<IProps> = ({ classes, value }) => (
<MenuItem
divider
component={"div" as ElementType}
className={classes.menuItem}
value={
value === SelectEnum.AllLabel
? SelectEnum.SelectNone
: SelectEnum.SelectAll
}
>
<Checkbox checked={value === SelectEnum.AllLabel} />
<ListItemText primary={"Select all"} />
</MenuItem>
);
If it can help this is how I fixed it: component={"div" as ElementType}
I know it's a hack but...const MenuItemSelectAll: React.SFC<IProps> = ({ classes, value }) => ( <MenuItem divider component={"div" as ElementType} className={classes.menuItem} value={ value === SelectEnum.AllLabel ? SelectEnum.SelectNone : SelectEnum.SelectAll } > <Checkbox checked={value === SelectEnum.AllLabel} /> <ListItemText primary={"Select all"} /> </MenuItem> );
What's your type definition for ElementType
? I get the error:
Cannot find name 'ElementType'
Code:
<Button component={"a" as ElementType}>foo</Button>
Just a reminder, #16487 does not solve the issue.
This works:
import React from "react";
import { Button } from '@material-ui/core';
import { MemoryRouter, Link } from 'react-router-dom';
function App() {
return (
<MemoryRouter>
<Button component={Link} to="http://www.google.com">hello</Button>
</MemoryRouter>
);
}
This does not work:
import React from "react";
import { Button } from '@material-ui/core';
import { ButtonProps } from '@material-ui/core/Button';
import { MemoryRouter, Link } from 'react-router-dom';
const MyButton = (props: ButtonProps) => <Button {...props} />
function App() {
return (
<MemoryRouter>
<MyButton component={Link} to="http://www.google.com">hello</MyButton>
</MemoryRouter>
);
}
This type problem is forcing react-admin to cast button props as any
...
Chiming in, we should be able to extend the types properly for all components.
Here's another example throwing the error (using the TypeScript compiler or Webpack):
import * as React from 'react';
import { Avatar } from '@material-ui/core';
import { AvatarProps } from '@material-ui/core/Avatar';
interface Props {
user: {
firstname: string;
lastname: string;
};
}
class UserAvatar extends React.Component<Props & AvatarProps> {
public render() {
const { user, ...props } = this.props;
return (
<Avatar {...props}>
{`${user.firstname.charAt(0)}${user.lastname.charAt(0)}`}
</Avatar>
);
}
}
export default UserAvatar;
Most of the time, we use this component this way:
<UserAvatar user={user} />
But there are situations where the avatar appears in a link <a>
. Since inline elements cannot contain block elements, the avatar must not be a div
but a span
. So we use it this way:
<a href={...}>
<UserAvatar user={user} component="span" />
</a>
That's where the compiler complains. Note that I have simplified the UserAvatar
component. The real component deals with background colors, a set of sizes and a default icon when user is not provided.
EDIT: I'm having a similar issue that I believe is related. In the following code, the compiler complains about secondaryTypographyProps.component
:
"Object literal may only specify known properties, and 'component' does not exist in type 'Partial
, "span">>'".
<ListItemText
secondary={`${user.firstname} ${user.lastname}`}
secondaryTypographyProps={{ component: 'span' }}
/>
@Zhouzi I think this use of generics ought to solve your issue:
import React, { ElementType, Component } from 'react';
import Avatar, { AvatarProps } from '@material-ui/core/Avatar';
type UserAvatarProps<
D extends ElementType = 'div',
P = {}
> = {
user: {
firstname: string;
lastname: string;
};
} & AvatarProps<D, P>;
export default class UserAvatar<
D extends ElementType = 'div',
P = {}
> extends Component<UserAvatarProps<D, P>> {
public render() {
const { user, ...avatarProps } = this.props;
return (
<Avatar {...avatarProps}>
{`${user.firstname.charAt(0)}${user.lastname.charAt(0)}`}
</Avatar>
);
}
}
Which can also be a functional component:
/* … */
export default function UserAvatar<
D extends ElementType = 'div',
P = {}
>({
user,
...avatarProps
}: UserAvatarProps<D, P>) {
return (
<Avatar {...avatarProps}>
{`${user.firstname.charAt(0)}${user.lastname.charAt(0)}`}
</Avatar>
);
}
Hi, still fairly new to React and MaterialUI I seem unable to find the appropriate solution on this topic. Whats the suggested way to achieve the following in Typescript:
As of the code below, IntelliJ is complaining with
Property 'component' does not exist on type 'IntrinsicAttributes & Pick<Pick<OverrideProps<ButtonTypeMap<{}, "button">, "button">, "form" | "slot" | "style" | "title" | ... 279 more ... | "startIcon">, "form" | ... 281 more ... | "startIcon"> & RefAttributes<...>'
import SendIcon from '@material-ui/icons/Send'
export const SaveButton = React.forwardRef<any, Omit<ButtonProps, 'variant' | 'color' | 'endIcon'>>((props, ref) => (
<Button ref={ref} variant="contained" color="primary" endIcon={<SendIcon/>} {...props} />
))
and
import { Link } from 'react-router-dom'
import { SaveButton } from '../SaveButton'
<SaveButton
component={Link}
to={`/step2`}
disabled={selectedItems.length === 0}
>
Save me!
</SaveButton>
Thanks for your help.
We ended up creating a wrapper and re-adding the ones we needed to a custom interface that extends from ButtonProps.
We ended up creating a wrapper and re-adding the ones we needed to a custom interface that extends from ButtonProps.
Thx, I'm casting mySaveButton as follows, which does the trick.
export const SaveButton = React.forwardRef<any, Omit<ButtonProps, 'variant' | 'color' | 'endIcon'>>((props, ref) => (
<Button ref={ref} variant="contained" color="primary" endIcon={<SendIcon/>} {...props} />
)) as ExtendButtonBase<ButtonTypeMap>
@kelly-tock Can you elaborate on your solution, please? I'm curious to see how you were able to maintain component
prop behavior.
import { Button as MuiButton } from '@material-ui/core';
import { ButtonProps as MuiButtonProps } from '@material-ui/core/Button';
export interface ButtonProps extends Pick<MuiButtonProps, Exclude<keyof MuiButtonProps, 'color'>> {
color?: ButtonColor;
component?: React.ElementType;
to?: string;
target?: string;
}
const Button: React.FunctionComponent<ButtonProps> = React.forwardRef(
we overrode color to our needs, then added things as we came across them.
const renderAppointmentDates = () => {
const dates = generateAppointmentDates();
return dates.map((date) => {
return (
<MenuItem
value={date}
component={MyButton}
key={date}
children={date}
/>
);
});
};
...
..
<Field
name="Select Day"
component={MaterialSelect}
{...{
initialValue: props.initialValues ? props.initialValues : "",
}}
>
<MenuItem value="" component={MyButton}></MenuItem>
{renderAppointmentDates()}
</Field>
I am facing the same problem here.
Error is
The `component` prop provided to ButtonBase is invalid.
Please make sure the children prop is rendered in this custom component.
import { Button as MuiButton } from '@material-ui/core'; import { ButtonProps as MuiButtonProps } from '@material-ui/core/Button'; export interface ButtonProps extends Pick<MuiButtonProps, Exclude<keyof MuiButtonProps, 'color'>> { color?: ButtonColor; component?: React.ElementType; to?: string; target?: string; } const Button: React.FunctionComponent<ButtonProps> = React.forwardRef(
we overrode color to our needs, then added things as we came across them.
May we see the implementation of const Button: React.FunctionComponent<ButtonProps> = React.forwardRef(
? Do you wrap a Router.Link around it if the to
prop is set? Do you need to cast the return value?
Still confused if its better to use
<Link to="/" component={SaveButton} />
or
<SaveButton to="/" component={Link} />
I appreciate your suggestions.
We have a component called Anchor
, which I can't share the full implementation of, but essentially if we want a button to be a link we do
<Button component={Anchor}
and Anchor for us has logic to determine if we're rendering a react router link or a regular link tag.
and for the Link
component in material ui, we default the component
prop to be our Anchor
we have a few cases where we want the page to just refresh, so there is some logic around that internal to Anchor
. Hope that helps.
Most helpful comment
To add to this, the following doesn't compile in TypeScript in MUIv4, but should:
error message is:
I'm using TypeScript 3.3.3, but also checked 3.4.5, and getting the same error