Skip to content

Commit 11b6652

Browse files
committed
Merge branch 'next' into menulist-tabindex
2 parents 7fa46a4 + 3044cb4 commit 11b6652

File tree

8 files changed

+127
-88
lines changed

8 files changed

+127
-88
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react';
2+
import { MemoryRouter as Router } from 'react-router';
3+
import { Link } from 'react-router-dom';
4+
import Button from '@material-ui/core/Button';
5+
6+
// required for react-router-dom < 6.0.0
7+
// see https://github.com/ReactTraining/react-router/issues/6056#issuecomment-435524678
8+
const AdapterLink = React.forwardRef((props, ref) => <Link innerRef={ref} {...props} />);
9+
10+
const HomeLink = React.forwardRef((props, ref) => <Link innerRef={ref} to="/home" {...props} />);
11+
12+
function App() {
13+
return (
14+
<Router>
15+
<Button color="primary" component={AdapterLink} to="/">
16+
Root
17+
</Button>
18+
{/* Avoids property collision */}
19+
<Button component={HomeLink}>Home</Button>
20+
</Router>
21+
);
22+
}
23+
24+
export default App;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react';
2+
import { MemoryRouter as Router } from 'react-router';
3+
import { Link, LinkProps } from 'react-router-dom';
4+
import Button from '@material-ui/core/Button';
5+
6+
// required for react-router-dom < 6.0.0
7+
// see https://github.com/ReactTraining/react-router/issues/6056#issuecomment-435524678
8+
const AdapterLink = React.forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => (
9+
<Link innerRef={ref as any} {...props} />
10+
));
11+
12+
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
13+
const HomeLink = React.forwardRef<HTMLAnchorElement, Omit<LinkProps, 'innerRef' | 'to'>>(
14+
(props, ref) => <Link innerRef={ref as any} to="/home" {...props} />,
15+
);
16+
17+
function App() {
18+
return (
19+
<Router>
20+
<Button color="primary" component={AdapterLink} to="/">
21+
Root
22+
</Button>
23+
{/* Avoids property collision */}
24+
<Button component={HomeLink}>Home</Button>
25+
</Router>
26+
);
27+
}
28+
29+
export default App;

docs/src/pages/demos/buttons/buttons.md

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -123,32 +123,7 @@ component forwards this ref to the underlying DOM node.
123123
Given that a lot of our interactive components rely on `ButtonBase`, you should be
124124
able to take advantage of it everywhere:
125125

126-
```jsx
127-
import React from 'react';
128-
import { Link as RouterLink } from 'react-router-dom'
129-
import Button from '@material-ui/core/Button';
126+
{{"demo": "pages/demos/buttons/ButtonRouter.js", "defaultCodeOpen": true}}
130127

131-
// required for react-router-dom < 5.0.0
132-
// see https://github.com/ReactTraining/react-router/issues/6056#issuecomment-435524678
133-
const Link = React.forwardRef((props, ref) => <RouterLink {...props} innerRef={ref} />)
134-
135-
<Button component={Link} to="/open-collective">
136-
Link
137-
</Button>
138-
```
139-
140-
or if you want to avoid properties collision:
141-
142-
```jsx
143-
import { Link } from 'react-router-dom'
144-
import Button from '@material-ui/core/Button';
145-
146-
// use `ref` instead of `innerRef` with react-router-dom@^5.0.0
147-
const MyLink = React.forwardRef((props, ref) => <Link to="/open-collective" {...props} innerRef={ref} />);
148-
149-
<Button component={MyLink}>
150-
Link
151-
</Button>
152-
```
153-
154-
*Note: Creating `MyLink` is necessary to prevent unexpected unmounting. You can read more about it in our [component property guide](/guides/composition/#component-property).*
128+
_Note: Creating the Button components is necessary to prevent unexpected unmounting.
129+
You can read more about it in our [component property guide](/guides/composition/#component-property)._
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import Switch from '@material-ui/core/Switch';
3+
4+
function Switches() {
5+
const [state, setState] = React.useState({
6+
checkedA: true,
7+
checkedB: true,
8+
});
9+
10+
const handleChange = (name: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
11+
setState({ ...state, [name]: event.target.checked });
12+
};
13+
14+
return (
15+
<div>
16+
<Switch checked={state.checkedA} onChange={handleChange('checkedA')} value="checkedA" />
17+
<Switch
18+
checked={state.checkedB}
19+
onChange={handleChange('checkedB')}
20+
value="checkedB"
21+
color="primary"
22+
/>
23+
<Switch value="checkedC" />
24+
<Switch disabled value="checkedD" />
25+
<Switch disabled checked value="checkedE" />
26+
<Switch defaultChecked value="checkedF" color="default" />
27+
</div>
28+
);
29+
}
30+
31+
export default Switches;

docs/src/pages/guides/typescript/typescript.md

Lines changed: 5 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -249,63 +249,10 @@ const theme = createMyTheme({ appDrawer: { breakpoint: 'md' }});
249249
```
250250

251251
## Usage of `component` property
252-
253252
Material-UI allows you to replace a component's root node via a `component` property.
254-
For example, a `Button`'s root node can be replaced with a React Router `Link`, and any additional props that are passed to `Button`, such as `to`, will be spread to the `Link` component, meaning you can do this:
255-
```jsx
256-
import { Link } from 'react-router-dom';
257-
258-
<Button component={Link} to="/">Go Home</Button>
259-
```
260-
261-
However, TypeScript will complain about it, because `to` is not part of the `ButtonProps` interface, and with the current type declarations it has no way of inferring what props can be passed to `component`.
262-
263-
The current workaround is to cast Link to `any`:
264-
265-
```tsx
266-
import { Link } from 'react-router-dom';
267-
import Button, { ButtonProps } from '@material-ui/core/Button';
268-
269-
interface LinkButtonProps extends ButtonProps {
270-
to: string;
271-
replace?: boolean;
272-
}
273-
274-
const LinkButton = (props: LinkButtonProps) => (
275-
<Button {...props} component={Link as any} />
276-
)
277-
278-
// usage:
279-
<LinkButton color="primary" to="/">Go Home</LinkButton>
280-
```
281-
282-
Material-UI components pass some basic event handler props (`onClick`, `onDoubleClick`, etc.) to their root nodes.
283-
These handlers have a signature of:
284-
```ts
285-
(event: MouseEvent<HTMLElement, MouseEvent>) => void
286-
```
287-
288-
which is incompatible with the event handler signatures that `Link` expects, which are:
289-
```ts
290-
(event: MouseEvent<AnchorElement>) => void
291-
```
292-
293-
Any element or component that you pass into `component` will have this problem if the signatures of their event handler props don't match.
294-
295-
There is an ongoing effort to fix this by making component props generic.
253+
For example, a `Button`'s root node can be replaced with a React Router `Link`, and any additional props that are passed to `Button`, such as `to`, will be spread to the `Link` component. For a code
254+
example concerning `Button` and `react-router-dom` checkout [this Button demo](/demos/buttons/#third-party-routing-library)
296255

297-
### Avoiding properties collision
298-
299-
The previous strategy suffers from a little limitation: properties collision.
300-
The component providing the `component` property might not forward all its properties to the root element.
301-
To workaround this issue, you can create a custom component:
302-
303-
```tsx
304-
import { Link } from 'react-router-dom';
305-
import Button from '@material-ui/core/Button';
306-
307-
const MyLink = (props: any) => <Link to="/" {...props} />;
308-
309-
// usage:
310-
<Button color="primary" component={MyLink}>Go Home</Button>
311-
```
256+
Not every component fully supports any component type you pass in. If you encounter a
257+
component that rejects its `component` props in TypeScript please open an issue.
258+
There is an ongoing effort to fix this by making component props generic.

packages/material-ui-styles/test/styles.spec.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,3 +385,37 @@ withStyles(theme =>
385385
color: number;
386386
}
387387
}
388+
389+
function forwardRefTest() {
390+
const styles = createStyles({
391+
root: { color: 'red' },
392+
});
393+
394+
function Anchor(props: WithStyles<typeof styles>) {
395+
const { classes } = props;
396+
return <a className={classes.root} />;
397+
}
398+
const StyledAnchor = withStyles(styles)(Anchor);
399+
400+
const anchorRef = React.useRef<HTMLAnchorElement>(null);
401+
// forwarded to function components which can't hold refs
402+
// property 'ref' does not exists
403+
<StyledAnchor ref={anchorRef} />; // $ExpectError
404+
<StyledAnchor innerRef={anchorRef} />;
405+
406+
const RefableAnchor = React.forwardRef<HTMLAnchorElement, WithStyles<typeof styles>>(
407+
(props, ref) => {
408+
const { classes } = props;
409+
return <a className={classes.root} />;
410+
},
411+
);
412+
const StyledRefableAnchor = withStyles(styles)(RefableAnchor);
413+
414+
<StyledRefableAnchor ref={anchorRef} />;
415+
const buttonRef = React.createRef<HTMLButtonElement>();
416+
// HTMLButtonElement is missing properties
417+
<StyledRefableAnchor ref={buttonRef} />; // $ExpectError
418+
// undesired: `innerRef` is currently typed as any but for backwards compat we're keeping it
419+
// especially since `innerRef` will be removed in v5 and is equivalent to `ref`
420+
<StyledRefableAnchor innerRef={buttonRef} />;
421+
}

packages/material-ui/src/Button/Button.spec.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React from 'react';
22
import Button, { ButtonProps } from '@material-ui/core/Button';
33
import { Link as ReactRouterLink, LinkProps } from 'react-router-dom';
4-
import { type } from 'os';
54

65
const log = console.log;
76

packages/material-ui/src/TextField/TextField.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ const TextField = React.forwardRef(function TextField(props, ref) {
100100
const labelNode = ReactDOM.findDOMNode(labelRef.current);
101101
setLabelWidth(labelNode != null ? labelNode.offsetWidth : 0);
102102
}
103-
}, [variant]);
103+
}, [variant, required]);
104104

105105
warning(
106106
!select || Boolean(children),

0 commit comments

Comments
 (0)