Sandbox here: https://codesandbox.io/s/agitated-ardinghelli-fnoj15?file=/src/temp4.tsx:0-1206.
import { FormControl, FormControlProps, Grid, GridProps } from "@mui/material";
interface ICustomControlProps {
gridProps?: GridProps;
formControlProps?: FormControlProps;
}
const CustomControl = (props: ICustomControlProps) => {
return (
<Grid {...props.gridProps}>
<FormControl {...props.formControlProps} />
</Grid>
);
};
const CustomControlVariant1 = (props: ICustomControlProps) => {
return (
<CustomControl
/**
* Below gridProps is of type GridProps.
* It is set to object with some fixed (item, xs, sm, md) properties and spreaded props.gridProps
* (which is of type GridProps).
* But doing exactly same with formControlProps doesnt work: setting fixed component properties
* and spreaded props.formControlProps
*/
gridProps={{ item: true, xs: 12, sm: 12, md: 12, ...props.gridProps }}
formControlProps={{
component: "fieldset", /** ERROR: Type '{ children?: React.ReactNode; classes?: Partial<FormControlClasses> &
* Partial<ClassNameMap<never>>; color?: "error" | ... 4 more ... | "warning"; ...
* 264 more ...; component: string; }' is not assignable to type 'FormControlProps<"div", {}>'.
* Object literal may only specify known properties, and 'component' does not exist in type
* 'FormControlProps<"div", {}>'.
*
* Object literal may only specify known properties, and 'component'
* does not exist in type 'FormControlProps<"div", {}>'.ts(2322)
*/
...props.formControlProps
}}
/>
);
};
Update
Note that adding another level of spreading for FormControl works:
<CustomControl
gridProps={{ item: true, xs: 12, sm: 12, md: 12, ...props.gridProps }}
// this works!! So, `component` is indeed on `FormControlProps`
// which can be confirmed here: https://mui.com/api/form-control/#props
formControlProps={{
...{
component: "fieldset",
...props.formControlProps
}
}}
/>
Here is the sandbox for this code.
I find both approaches logically correct and in my opinion, both should work, but I don't understand why the first one is giving an error.
In order to make TypeScript happy, you could modify ICustomControlProps as follows:
interface ICustomControlProps {
gridProps?: GridProps;
formControlProps?: FormControlProps & { component: React.ElementType };
}
Let's have a look in the FormControl's declaration file:
declare const FormControl: OverridableComponent<FormControlTypeMap>;
export type FormControlProps<
D extends React.ElementType = FormControlTypeMap['defaultComponent'],
P = {},
> = OverrideProps<FormControlTypeMap<P, D>, D>;
Concisely, FormControlProps will gather together the props of the default component(which is a div - so, some props a specific DOM element can take in) and the props which define the FormControl component(these can be found in FormControlTypeMap, e.g. classes, fullWidth, hiddenLabel etc).
In FormControlTypeMap there surely is no component property defined and hence the error. However, OverridableComponent<FormControlTypeMap> says that receiving a component prop is accepted:
export interface OverridableComponent<M extends OverridableTypeMap> {
<C extends React.ElementType>(
props: {
/**
* The component used for the root node.
* Either a string to use a HTML element or a component.
*/
component: C;
} & OverrideProps<M, C>,
): JSX.Element;
(props: DefaultComponentProps<M>): JSX.Element;
}
So, you can safely use FormControl like this: <FormControl component={...} />, but wrapping it in another component, like this:
interface ICustomControlProps {
gridProps?: GridProps;
formControlProps?: FormControlProps;
}
const CustomControl = (props: ICustomControlProps) => {
return (
<Grid {...props.gridProps}>
<FormControl {...props.formControlProps} />
</Grid>
);
};
will require you to specify that a component prop is also allowed on ICustomControlProps.formControlProps:
interface ICustomControlProps {
gridProps?: GridProps;
formControlProps?: FormControlProps & { component: React.ElementType };
}
As to why this works:
formControlProps={{
...{
component: "fieldset",
...props.formControlProps
}
}}
the answer seems to be found in this issue, more specifically in this comment, which says that ... creates a subtype, so it's all valid.