Skip to content

Commit

Permalink
[core] Remove unstable_StrictMode transition components (#20952)
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon authored May 9, 2020
1 parent 8c6fe14 commit 1f68ceb
Show file tree
Hide file tree
Showing 45 changed files with 561 additions and 247 deletions.
1 change: 1 addition & 0 deletions docs/pages/api-docs/collapse.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ The `MuiCollapse` name can be used for providing [default props](/customization/
| <span class="prop-name">classes</span> | <span class="prop-type">object</span> | | Override or extend the styles applied to the component. See [CSS API](#css) below for more details. |
| <span class="prop-name">collapsedHeight</span> | <span class="prop-type">number<br>&#124;&nbsp;string</span> | <span class="prop-default">'0px'</span> | The height of the container when collapsed. |
| <span class="prop-name">component</span> | <span class="prop-type">elementType</span> | <span class="prop-default">'div'</span> | The component used for the root node. Either a string to use a HTML element or a component. |
| <span class="prop-name">disableStrictModeCompat</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | Enable this prop if you encounter 'Function components cannot be given refs', use `unstable_createStrictModeTheme`, and can't forward the ref in the passed `Component`. |
| <span class="prop-name">in</span> | <span class="prop-type">bool</span> | | If `true`, the component will transition in. |
| <span class="prop-name">timeout</span> | <span class="prop-type">'auto'<br>&#124;&nbsp;number<br>&#124;&nbsp;{ appear?: number, enter?: number, exit?: number }</span> | <span class="prop-default">duration.standard</span> | The duration for the transition, in milliseconds. You may specify a single timeout for all transitions, or individually with an object.<br>Set to 'auto' to automatically calculate transition time based on height. |

Expand Down
1 change: 1 addition & 0 deletions docs/pages/api-docs/fade.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ It uses [react-transition-group](https://github.com/reactjs/react-transition-gro
| Name | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| <span class="prop-name">children</span> | <span class="prop-type">element</span> | | A single child content element. |
| <span class="prop-name">disableStrictModeCompat</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | Enable this prop if you encounter 'Function components cannot be given refs', use `unstable_createStrictModeTheme`, and can't forward the ref in the child component. |
| <span class="prop-name">in</span> | <span class="prop-type">bool</span> | | If `true`, the component will transition in. |
| <span class="prop-name">timeout</span> | <span class="prop-type">number<br>&#124;&nbsp;{ appear?: number, enter?: number, exit?: number }</span> | <span class="prop-default">{ enter: duration.enteringScreen, exit: duration.leavingScreen,}</span> | The duration for the transition, in milliseconds. You may specify a single timeout for all transitions, or individually with an object. |

Expand Down
1 change: 1 addition & 0 deletions docs/pages/api-docs/grow.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ It uses [react-transition-group](https://github.com/reactjs/react-transition-gro
| Name | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| <span class="prop-name">children</span> | <span class="prop-type">element</span> | | A single child content element. |
| <span class="prop-name">disableStrictModeCompat</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | Enable this prop if you encounter 'Function components cannot be given refs', use `unstable_createStrictModeTheme`, and can't forward the ref in the child component. |
| <span class="prop-name">in</span> | <span class="prop-type">bool</span> | | If `true`, show the component; triggers the enter or exit animation. |
| <span class="prop-name">timeout</span> | <span class="prop-type">'auto'<br>&#124;&nbsp;number<br>&#124;&nbsp;{ appear?: number, enter?: number, exit?: number }</span> | <span class="prop-default">'auto'</span> | The duration for the transition, in milliseconds. You may specify a single timeout for all transitions, or individually with an object.<br>Set to 'auto' to automatically calculate transition time based on height. |

Expand Down
1 change: 1 addition & 0 deletions docs/pages/api-docs/zoom.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ It uses [react-transition-group](https://github.com/reactjs/react-transition-gro
| Name | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| <span class="prop-name">children</span> | <span class="prop-type">element</span> | | A single child content element. |
| <span class="prop-name">disableStrictModeCompat</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | Enable this prop if you encounter 'Function components cannot be given refs', use `unstable_createStrictModeTheme`, and can't forward the ref in the child component. |
| <span class="prop-name">in</span> | <span class="prop-type">bool</span> | | If `true`, the component will transition in. |
| <span class="prop-name">timeout</span> | <span class="prop-type">number<br>&#124;&nbsp;{ appear?: number, enter?: number, exit?: number }</span> | <span class="prop-default">{ enter: duration.enteringScreen, exit: duration.leavingScreen,}</span> | The duration for the transition, in milliseconds. You may specify a single timeout for all transitions, or individually with an object. |

Expand Down
2 changes: 1 addition & 1 deletion docs/src/modules/components/AppDrawerNavItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import clsx from 'clsx';
import { makeStyles } from '@material-ui/core/styles';
import ListItem from '@material-ui/core/ListItem';
import Button from '@material-ui/core/Button';
import { unstable_StrictModeCollapse as Collapse } from '@material-ui/core/Collapse';
import Collapse from '@material-ui/core/Collapse';
import Link from 'docs/src/modules/components/Link';

const useStyles = makeStyles((theme) => ({
Expand Down
4 changes: 2 additions & 2 deletions docs/src/modules/components/Demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { useSelector, useDispatch } from 'react-redux';
import { fade, makeStyles, useTheme } from '@material-ui/core/styles';
import IconButton from '@material-ui/core/IconButton';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import { unstable_StrictModeCollapse as Collapse } from '@material-ui/core/Collapse';
import { unstable_StrictModeFade as Fade } from '@material-ui/core/Fade';
import Collapse from '@material-ui/core/Collapse';
import Fade from '@material-ui/core/Fade';
import ToggleButton from '@material-ui/lab/ToggleButton';
import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
import { JavaScript as JavaScriptIcon, TypeScript as TypeScriptIcon } from '@material-ui/docs';
Expand Down
8 changes: 5 additions & 3 deletions docs/src/modules/components/HighlightedCode.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import * as PropTypes from 'prop-types';
import MarkdownElement from './MarkdownElement';
import prism from 'docs/src/modules/utils/prism';

export default function HighlightedCode(props) {
const HighlightedCode = React.forwardRef(function HighlightedCode(props, ref) {
const { code, language, ...other } = props;
const renderedCode = React.useMemo(() => {
return prism(code.trim(), language);
}, [code, language]);

return (
<MarkdownElement {...other}>
<MarkdownElement ref={ref} {...other}>
<pre>
<code
className={`language-${language}`}
Expand All @@ -20,9 +20,11 @@ export default function HighlightedCode(props) {
</pre>
</MarkdownElement>
);
}
});

HighlightedCode.propTypes = {
code: PropTypes.string.isRequired,
language: PropTypes.string.isRequired,
};

export default HighlightedCode;
15 changes: 12 additions & 3 deletions docs/src/modules/components/MarkdownElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ const styles = (theme) => ({
});
const useStyles = makeStyles(styles, { name: 'MarkdownElement', flip: false });

export default function MarkdownElement(props) {
const MarkdownElement = React.forwardRef(function MarkdownElement(props, ref) {
const { className, renderedMarkdown, ...other } = props;
const classes = useStyles();
const more = {};
Expand All @@ -213,10 +213,19 @@ export default function MarkdownElement(props) {
more.dangerouslySetInnerHTML = { __html: renderedMarkdown };
}

return <div className={clsx(classes.root, 'markdown-body', className)} {...more} {...other} />;
}
return (
<div
className={clsx(classes.root, 'markdown-body', className)}
{...more}
{...other}
ref={ref}
/>
);
});

MarkdownElement.propTypes = {
className: PropTypes.string,
renderedMarkdown: PropTypes.string,
};

export default MarkdownElement;
2 changes: 1 addition & 1 deletion docs/src/modules/components/Notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import IconButton from '@material-ui/core/IconButton';
import Badge from '@material-ui/core/Badge';
import Typography from '@material-ui/core/Typography';
import Popper from '@material-ui/core/Popper';
import { unstable_StrictModeGrow as Grow } from '@material-ui/core/Grow';
import Grow from '@material-ui/core/Grow';
import Paper from '@material-ui/core/Paper';
import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import List from '@material-ui/core/List';
Expand Down
97 changes: 97 additions & 0 deletions docs/src/pages/customization/theming/theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,100 @@ import { createMuiTheme, responsiveFontSizes } from '@material-ui/core/styles';
let theme = createMuiTheme();
theme = responsiveFontSizes(theme);
```

### `unstable_createMuiStrictModeTheme(options, ...args) => theme`

**WARNING**: Do not use this method in production.

Generates a theme that reduces the amount of warnings inside [`React.StrictMode`](https://reactjs.org/docs/strict-mode.html) like `Warning: findDOMNode is deprecated in StrictMode`.

#### Requirements

Using `unstable_createMuiStrictModeTheme` restricts the usage of some of our components.

##### `component` prop

The component used in the `component` prop of the following components need to forward their ref:

- [`Collapse`](/api/Collapse/)

Otherwise you'll encounter `Error: Function component cannot be given refs`.
See also: [Composition: Caveat with refs](/guides/composition/#caveat-with-refs).

##### `children` prop

The `children` of the following components need to forward their ref:

- [`Fade`](/api/Fade/)
- [`Grow`](/api/Grow/)
- [`Zoom`](/api/Zoom/)

```diff
-function TabPanel(props) {
+const TabPanel = React.forwardRef(function TabPanel(props, ref) {
return <div role="tabpanel" {...props} ref={ref} />;
-}
+});

function Tabs() {
return <Fade><TabPanel>...</TabPanel></Fade>;
}
```

Otherwise the component will not animate properly and you'll encounter the warning that `Function components cannot be given refs`.

#### Disable StrictMode compatibility partially

If you still see `Error: Function component cannot be given refs` then you're probably using a third-party component for which the previously mentioned fixes aren't applicable.
You can fix this by applying `disableStrictModeCompat`. You'll see deprecation warnings again but these are only warnings while
`Function component cannot be given refs` actually breaks the documented behavior of our components.

```diff
import { unstable_createMuiStrictModeTheme } from '@material-ui/core/styles';

function ThirdPartyTabPanel(props) {
return <div {...props} role="tabpanel">
}

const theme = unstable_createMuiStrictModeTheme();

function Fade() {
return (
<React.StrictMode>
<ThemeProvider theme={theme}>
- <Fade>
+ <Fade disableStrictModeCompat>
<ThirdPartyTabPanel />
</Fade>
</ThemeProvider>
</React.StrictMode>,
);
}
```

#### Arguments

1. `options` (_Object_): Takes an incomplete theme object and adds the missing parts.
2. `...args` (_Array_): Deep merge the arguments with the about to be returned theme.

#### Returns

`theme` (_Object_): A complete, ready to use theme object.

#### Examples

```js
import { unstable_createMuiStrictModeTheme } from '@material-ui/core/styles';

const theme = unstable_createMuiStrictModeTheme();

function App() {
return (
<React.StrictMode>
<ThemeProvider theme={theme}>
<LandingPage />
</ThemeProvider>
</React.StrictMode>,
);
}
````
3 changes: 1 addition & 2 deletions packages/material-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
},
"dependencies": {
"@babel/runtime": "^7.4.4",
"@material-ui/react-transition-group": "^4.3.0",
"@material-ui/styles": "^4.9.13",
"@material-ui/system": "^4.9.13",
"@material-ui/types": "^5.0.1",
Expand All @@ -60,7 +59,7 @@
"popper.js": "^1.16.1-lts",
"prop-types": "^15.7.2",
"react-is": "^16.8.0",
"react-transition-group": "^4.3.0"
"react-transition-group": "^4.4.0"
},
"devDependencies": {},
"sideEffects": false,
Expand Down
6 changes: 6 additions & 0 deletions packages/material-ui/src/Collapse/Collapse.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ export interface CollapseProps extends StandardProps<TransitionProps, CollapseCl
* Either a string to use a HTML element or a component.
*/
component?: React.ElementType<TransitionProps>;
/**
* Enable this prop if you encounter 'Function components cannot be given refs',
* use `unstable_createStrictModeTheme`,
* and can't forward the ref in the passed `Component`.
*/
disableStrictModeCompat?: boolean;
/**
* If `true`, the component will transition in.
*/
Expand Down
55 changes: 43 additions & 12 deletions packages/material-ui/src/Collapse/Collapse.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import withStyles from '../styles/withStyles';
import { duration } from '../styles/transitions';
import { getTransitionProps } from '../transitions/utils';
import useTheme from '../styles/useTheme';
import { useForkRef } from '../utils';

export const styles = (theme) => ({
/* Styles applied to the container element. */
Expand Down Expand Up @@ -46,11 +47,13 @@ const Collapse = React.forwardRef(function Collapse(props, ref) {
className,
collapsedHeight: collapsedHeightProp = '0px',
component: Component = 'div',
disableStrictModeCompat = false,
in: inProp,
onEnter,
onEntered,
onEntering,
onExit,
onExited,
onExiting,
style,
timeout = duration.standard,
Expand All @@ -71,15 +74,28 @@ const Collapse = React.forwardRef(function Collapse(props, ref) {
};
}, []);

const handleEnter = (node, isAppearing) => {
const enableStrictModeCompat = theme.unstable_strictMode && !disableStrictModeCompat;
const nodeRef = React.useRef(null);
const handleRef = useForkRef(ref, enableStrictModeCompat ? nodeRef : undefined);

const normalizedTransitionCallback = (callback) => (nodeOrAppearing, maybeAppearing) => {
if (callback) {
const [node, isAppearing] = enableStrictModeCompat
? [nodeRef.current, nodeOrAppearing]
: [nodeOrAppearing, maybeAppearing];
callback(node, isAppearing);
}
};

const handleEnter = normalizedTransitionCallback((node, isAppearing) => {
node.style.height = collapsedHeight;

if (onEnter) {
onEnter(node, isAppearing);
}
};
});

const handleEntering = (node, isAppearing) => {
const handleEntering = normalizedTransitionCallback((node, isAppearing) => {
const wrapperHeight = wrapperRef.current ? wrapperRef.current.clientHeight : 0;

const { duration: transitionDuration } = getTransitionProps(
Expand All @@ -103,26 +119,28 @@ const Collapse = React.forwardRef(function Collapse(props, ref) {
if (onEntering) {
onEntering(node, isAppearing);
}
};
});

const handleEntered = (node, isAppearing) => {
const handleEntered = normalizedTransitionCallback((node, isAppearing) => {
node.style.height = 'auto';

if (onEntered) {
onEntered(node, isAppearing);
}
};
});

const handleExit = (node) => {
const handleExit = normalizedTransitionCallback((node) => {
const wrapperHeight = wrapperRef.current ? wrapperRef.current.clientHeight : 0;
node.style.height = `${wrapperHeight}px`;

if (onExit) {
onExit(node);
}
};
});

const handleExiting = (node) => {
const handleExited = normalizedTransitionCallback(onExited);

const handleExiting = normalizedTransitionCallback((node) => {
const wrapperHeight = wrapperRef.current ? wrapperRef.current.clientHeight : 0;

const { duration: transitionDuration } = getTransitionProps(
Expand All @@ -146,9 +164,10 @@ const Collapse = React.forwardRef(function Collapse(props, ref) {
if (onExiting) {
onExiting(node);
}
};
});

const addEndListener = (_, next) => {
const addEndListener = (nodeOrNext, maybeNext) => {
const next = enableStrictModeCompat ? nodeOrNext : maybeNext;
if (timeout === 'auto') {
timer.current = setTimeout(next, autoTransitionDuration.current || 0);
}
Expand All @@ -161,8 +180,10 @@ const Collapse = React.forwardRef(function Collapse(props, ref) {
onEntered={handleEntered}
onEntering={handleEntering}
onExit={handleExit}
onExited={handleExited}
onExiting={handleExiting}
addEndListener={addEndListener}
nodeRef={enableStrictModeCompat ? nodeRef : undefined}
timeout={timeout === 'auto' ? null : timeout}
{...other}
>
Expand All @@ -180,7 +201,7 @@ const Collapse = React.forwardRef(function Collapse(props, ref) {
minHeight: collapsedHeight,
...style,
}}
ref={ref}
ref={handleRef}
{...childProps}
>
<div className={classes.wrapper} ref={wrapperRef}>
Expand Down Expand Up @@ -219,6 +240,12 @@ Collapse.propTypes = {
* Either a string to use a HTML element or a component.
*/
component: PropTypes.elementType,
/**
* Enable this prop if you encounter 'Function components cannot be given refs',
* use `unstable_createStrictModeTheme`,
* and can't forward the ref in the passed `Component`.
*/
disableStrictModeCompat: PropTypes.bool,
/**
* If `true`, the component will transition in.
*/
Expand All @@ -239,6 +266,10 @@ Collapse.propTypes = {
* @ignore
*/
onExit: PropTypes.func,
/**
* @ignore
*/
onExited: PropTypes.func,
/**
* @ignore
*/
Expand Down
Loading

0 comments on commit 1f68ceb

Please sign in to comment.