Skip to content

Commit 63d2dfa

Browse files
[docs][material-ui] Revamp Composition guide (#43266)
Signed-off-by: Zeeshan Tamboli <[email protected]> Co-authored-by: Aarón García Hervás <[email protected]>
1 parent a9292a0 commit 63d2dfa

File tree

4 files changed

+22
-109
lines changed

4 files changed

+22
-109
lines changed

docs/data/material/components/tooltips/tooltips.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,6 @@ const MyComponent = React.forwardRef(function MyComponent(props, ref) {
7171
</Tooltip>;
7272
```
7373

74-
You can find a similar concept in the [wrapping components](/material-ui/guides/composition/#wrapping-components) guide.
75-
7674
If using a class component as a child, you'll also need to ensure that the ref is forwarded to the underlying DOM element. (A ref to the class component itself will not work.)
7775

7876
```jsx

docs/data/material/guides/composition/composition.md

Lines changed: 20 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -26,106 +26,44 @@ WrappedIcon.muiName = Icon.muiName;
2626

2727
Material UI allows you to change the root element that will be rendered via a prop called `component`.
2828

29-
### How does it work?
30-
31-
The custom component will be rendered by Material UI like this:
32-
33-
```js
34-
return React.createElement(props.component, props);
35-
```
36-
3729
For example, by default a `List` component will render a `<ul>` element.
3830
This can be changed by passing a [React component](https://react.dev/reference/react/Component) to the `component` prop.
39-
The following example will render the `List` component with a `<nav>` element as root element instead:
31+
The following example renders the `List` component with a `<menu>` element as root element instead:
4032

4133
```jsx
42-
<List component="nav">
43-
<ListItem button>
44-
<ListItemText primary="Trash" />
34+
<List component="menu">
35+
<ListItem>
36+
<ListItemButton>
37+
<ListItemText primary="Trash" />
38+
</ListItemButton>
4539
</ListItem>
46-
<ListItem button>
47-
<ListItemText primary="Spam" />
40+
<ListItem>
41+
<ListItemButton>
42+
<ListItemText primary="Spam" />
43+
</ListItemButton>
4844
</ListItem>
4945
</List>
5046
```
5147

5248
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.
53-
But it also **comes with a small caveat!**
54-
55-
### Inlining & caveat
56-
57-
Using an inline function as an argument for the `component` prop may result in **unexpected unmounting**, since a new component is passed every time React renders.
58-
For instance, if you want to create a custom `ListItem` that acts as a link, you could do the following:
59-
60-
```jsx
61-
import { Link } from 'react-router-dom';
62-
63-
function ListItemLink(props) {
64-
const { icon, primary, to } = props;
65-
66-
const CustomLink = (props) => <Link to={to} {...props} />;
67-
68-
return (
69-
<li>
70-
<ListItem button component={CustomLink}>
71-
<ListItemIcon>{icon}</ListItemIcon>
72-
<ListItemText primary={primary} />
73-
</ListItem>
74-
</li>
75-
);
76-
}
77-
```
7849

79-
:::warning
80-
However, since we are using an inline function to change the rendered component, React will remount the link every time `ListItemLink` is rendered. Not only will React update the DOM unnecessarily but the state will be lost, for example the ripple effect of the `ListItem` will also not work correctly.
81-
:::
50+
### Passing other React components
8251

83-
The solution is simple: **avoid inline functions and pass a static component to the `component` prop** instead.
84-
Let's change the `ListItemLink` component so `CustomLink` always reference the same component:
52+
You can pass any other React component to `component` prop. For example, you can pass `Link` component from `react-router-dom`:
8553

8654
```tsx
87-
import { Link, LinkProps } from 'react-router-dom';
88-
89-
function ListItemLink(props) {
90-
const { icon, primary, to } = props;
91-
92-
const CustomLink = React.useMemo(
93-
() =>
94-
React.forwardRef<HTMLAnchorElement, Omit<RouterLinkProps, 'to'>>(
95-
function Link(linkProps, ref) {
96-
return <Link ref={ref} to={to} {...linkProps} />;
97-
},
98-
),
99-
[to],
100-
);
55+
import { Link } from 'react-router-dom';
56+
import Button from '@mui/material/Button';
10157

58+
function Demo() {
10259
return (
103-
<li>
104-
<ListItem button component={CustomLink}>
105-
<ListItemIcon>{icon}</ListItemIcon>
106-
<ListItemText primary={primary} />
107-
</ListItem>
108-
</li>
60+
<Button component={Link} to="/react-router">
61+
React router link
62+
</Button>
10963
);
11064
}
11165
```
11266

113-
### Prop forwarding & caveat
114-
115-
You can take advantage of the prop forwarding to simplify the code.
116-
In this example, we don't create any intermediary component:
117-
118-
```jsx
119-
import { Link } from 'react-router-dom';
120-
121-
<ListItem button component={Link} to="/">
122-
```
123-
124-
:::warning
125-
However, this strategy suffers from a limitation: prop name collisions.
126-
The component receiving the `component` prop (for example ListItem) might intercept the prop (for example to) that is destined to the leaf element (for example Link).
127-
:::
128-
12967
### With TypeScript
13068

13169
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.
@@ -148,9 +86,9 @@ The other props of the `Typography` component will also be present in props of t
14886

14987
You can find a code example with the Button and react-router-dom in [these demos](/material-ui/integrations/routing/#component-prop).
15088

151-
#### Generic
89+
### Generic
15290

153-
It's also possible to have a generic `CustomComponent` which will accept any React component, and HTML elements.
91+
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).
15492

15593
```ts
15694
function GenericCustomComponent<C extends React.ElementType>(

docs/data/material/integrations/routing/ListRouter.js

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,7 @@ import Divider from '@mui/material/Divider';
1111
import InboxIcon from '@mui/icons-material/Inbox';
1212
import DraftsIcon from '@mui/icons-material/Drafts';
1313
import Typography from '@mui/material/Typography';
14-
import {
15-
Link as RouterLink,
16-
Route,
17-
Routes,
18-
MemoryRouter,
19-
useLocation,
20-
} from 'react-router-dom';
14+
import { Link, Route, Routes, MemoryRouter, useLocation } from 'react-router-dom';
2115
import { StaticRouter } from 'react-router-dom/server';
2216

2317
function Router(props) {
@@ -37,10 +31,6 @@ Router.propTypes = {
3731
children: PropTypes.node,
3832
};
3933

40-
const Link = React.forwardRef(function Link(itemProps, ref) {
41-
return <RouterLink ref={ref} {...itemProps} role={undefined} />;
42-
});
43-
4434
function ListItemLink(props) {
4535
const { icon, primary, to } = props;
4636

docs/data/material/integrations/routing/ListRouter.tsx

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,7 @@ import Divider from '@mui/material/Divider';
1010
import InboxIcon from '@mui/icons-material/Inbox';
1111
import DraftsIcon from '@mui/icons-material/Drafts';
1212
import Typography from '@mui/material/Typography';
13-
import {
14-
Link as RouterLink,
15-
LinkProps as RouterLinkProps,
16-
Route,
17-
Routes,
18-
MemoryRouter,
19-
useLocation,
20-
} from 'react-router-dom';
13+
import { Link, Route, Routes, MemoryRouter, useLocation } from 'react-router-dom';
2114
import { StaticRouter } from 'react-router-dom/server';
2215

2316
function Router(props: { children?: React.ReactNode }) {
@@ -39,12 +32,6 @@ interface ListItemLinkProps {
3932
to: string;
4033
}
4134

42-
const Link = React.forwardRef<HTMLAnchorElement, RouterLinkProps>(
43-
function Link(itemProps, ref) {
44-
return <RouterLink ref={ref} {...itemProps} role={undefined} />;
45-
},
46-
);
47-
4835
function ListItemLink(props: ListItemLinkProps) {
4936
const { icon, primary, to } = props;
5037

0 commit comments

Comments
 (0)