Files
react-test/docs/data/material/guides/composition/composition.md
how2ice 005cf56baf
Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
init project
2025-12-12 14:26:25 +09:00

245 lines
8.9 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Composition
<p class="description">Material UI tries to make composition as easy as possible.</p>
## Wrapping components
To provide maximum flexibility and performance, Material UI needs a way to know the nature of the child elements a component receives.
To solve this problem, we tag some of the components with a `muiName` static property when needed.
You may, however, need to wrap a component in order to enhance it, which can conflict with the `muiName` solution.
If you wrap a component, verify if that component has this static property set.
If you encounter this issue, you need to use the same tag for your wrapping component that is used with the wrapped component.
In addition, you should forward the props, as the parent component may need to control the wrapped components props.
Let's see an example:
```jsx
const WrappedIcon = (props) => <Icon {...props} />;
WrappedIcon.muiName = Icon.muiName;
```
{{"demo": "Composition.js"}}
### Forwarding slot props
Use the `mergeSlotProps` utility function to merge custom props with the slot props.
If the arguments are functions then they'll be resolved before merging, and the result from the first argument will override the second.
Special properties that merged between the two arguments are listed below:
- `className`: values are concatenated rather than overriding one another.
In the snippet below, the `custom-tooltip-popper` class is applied to the Tooltip's popper slot.
```jsx
import Tooltip, { TooltipProps } from '@mui/material/Tooltip';
import { mergeSlotProps } from '@mui/material/utils';
export const CustomTooltip = (props: TooltipProps) => {
const { children, title, sx: sxProps } = props;
return (
<Tooltip
{...props}
title={<Box sx={{ p: 4 }}>{title}</Box>}
slotProps={{
...props.slotProps,
popper: mergeSlotProps(props.slotProps?.popper, {
className: 'custom-tooltip-popper',
disablePortal: true,
placement: 'top',
}),
}}
>
{children}
</Tooltip>
);
};
```
If you added another `className` via the `slotProps` prop on the Custom Tooltip—as shown below—then both would be present on the rendered popper slot:
```js
<CustomTooltip slotProps={{ popper: { className: 'foo' } }} />
```
The popper slot in the original example would now have both classes applied to it, in addition to any others that may be present: `"[…] custom-tooltip-popper foo"`.
- `style`: object are shallow merged rather than replacing one another. The style keys from the first argument have higher priority.
- `sx`: values are concatenated into an array.
- `^on[A-Z]` event handlers: these functions are composed between the two arguments.
```js
mergeSlotProps(props.slotProps?.popper, {
onClick: (event) => {}, // composed with the `slotProps?.popper?.onClick`
createPopper: (popperOptions) => {}, // overridden by the `slotProps?.popper?.createPopper`
});
```
## Component prop
Material UI allows you to change the root element that will be rendered via a prop called `component`.
For example, by default a `List` component will render a `<ul>` element.
This can be changed by passing a [React component](https://react.dev/reference/react/Component) to the `component` prop.
The following example renders the `List` component with a `<menu>` element as root element instead:
```jsx
<List component="menu">
<ListItem>
<ListItemButton>
<ListItemText primary="Trash" />
</ListItemButton>
</ListItem>
<ListItem>
<ListItemButton>
<ListItemText primary="Spam" />
</ListItemButton>
</ListItem>
</List>
```
This pattern is very powerful and allows for great flexibility, as well as a way to interoperate with other libraries, such as your favorite routing or forms library.
### Passing other React components
You can pass any other React component to `component` prop. For example, you can pass `Link` component from `react-router`:
```tsx
import { Link } from 'react-router';
import Button from '@mui/material/Button';
function Demo() {
return (
<Button component={Link} to="/react-router">
React router link
</Button>
);
}
```
### With TypeScript
To be able to use the `component` prop, the type of the props should be used with type arguments. Otherwise, the `component` prop will not be present.
The examples below use `TypographyProps` but the same will work for any component which has props defined with `OverrideProps`.
```ts
import { TypographyProps } from '@mui/material/Typography';
function CustomComponent(props: TypographyProps<'a', { component: 'a' }>) {
/* ... */
}
// ...
<CustomComponent component="a" />;
```
Now the `CustomComponent` can be used with a `component` prop which should be set to `'a'`.
In addition, the `CustomComponent` will have all props of a `<a>` HTML element.
The other props of the `Typography` component will also be present in props of the `CustomComponent`.
You can find a code example with the Button and react-router in [these demos](/material-ui/integrations/routing/#component-prop).
### Generic
It's also possible to have a generic custom component which accepts any React component, including [built-in components](https://react.dev/reference/react-dom/components/common).
```ts
function GenericCustomComponent<C extends React.ElementType>(
props: TypographyProps<C, { component?: C }>,
) {
/* ... */
}
```
If the `GenericCustomComponent` is used with a `component` prop provided, it should also have all props required by the provided component.
```ts
function ThirdPartyComponent({ prop1 }: { prop1: string }) {
/* ... */
}
// ...
<GenericCustomComponent component={ThirdPartyComponent} prop1="some value" />;
```
The `prop1` became required for the `GenericCustomComponent` as the `ThirdPartyComponent` has it as a requirement.
Not every component fully supports any component type you pass in.
If you encounter a component that rejects its `component` props in TypeScript, please open an issue.
There is an ongoing effort to fix this by making component props generic.
## Caveat with refs
This section covers caveats when using a custom component as `children` or for the
`component` prop.
Some of the components need access to the DOM node. This was previously possible
by using `ReactDOM.findDOMNode`. This function is deprecated in favor of `ref` and
ref forwarding. However, only the following component types can be given a `ref`:
- Any Material UI component
- class components, that is `React.Component` or `React.PureComponent`
- DOM (or host) components, for example `div` or `button`
- [React.forwardRef components](https://react.dev/reference/react/forwardRef)
- [React.lazy components](https://react.dev/reference/react/lazy)
- [React.memo components](https://react.dev/reference/react/memo)
If you don't use one of the above types when using your components in conjunction with Material UI, you might see a warning from
React in your console similar to:
:::warning
Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
:::
Note that you will still get this warning for `lazy` and `memo` components if their wrapped component can't hold a ref.
In some instances, an additional warning is issued to help with debugging, similar to:
:::warning
Invalid prop `component` supplied to `ComponentName`. Expected an element type that can hold a ref.
:::
Only the two most common use cases are covered. For more information see [this section in the official React docs](https://react.dev/reference/react/forwardRef).
```diff
-const MyButton = () => <div role="button" />;
+const MyButton = React.forwardRef((props, ref) =>
+ <div role="button" {...props} ref={ref} />);
<Button component={MyButton} />;
```
```diff
-const SomeContent = props => <div {...props}>Hello, World!</div>;
+const SomeContent = React.forwardRef((props, ref) =>
+ <div {...props} ref={ref}>Hello, World!</div>);
<Tooltip title="Hello again."><SomeContent /></Tooltip>;
```
To find out if the Material UI component you're using has this requirement, check
out the props API documentation for that component. If you need to forward refs
the description will link to this section.
### Caveat with StrictMode
If you use class components for the cases described above you will still see
warnings in `React.StrictMode`.
`ReactDOM.findDOMNode` is used internally for backwards compatibility.
You can use `React.forwardRef` and a designated prop in your class component to forward the `ref` to a DOM component.
Doing so should not trigger any more warnings related to the deprecation of `ReactDOM.findDOMNode`.
```diff
class Component extends React.Component {
render() {
- const { props } = this;
+ const { forwardedRef, ...props } = this.props;
return <div {...props} ref={forwardedRef} />;
}
}
-export default Component;
+export default React.forwardRef((props, ref) => <Component {...props} forwardedRef={ref} />);
```