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.
Santiago Trujillo
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.