diff --git a/docs/src/modules/components/AppDrawerNavItem.js b/docs/src/modules/components/AppDrawerNavItem.js index a9cb7ec801d877..8ff3f5daf13422 100644 --- a/docs/src/modules/components/AppDrawerNavItem.js +++ b/docs/src/modules/components/AppDrawerNavItem.js @@ -82,9 +82,9 @@ class AppDrawerNavItem extends React.Component { return ( +
@@ -158,13 +160,7 @@ function HomeSteps(props) { />
- +
diff --git a/docs/src/modules/components/PageTitle.js b/docs/src/modules/components/PageTitle.js index 2cfa75043359cd..c9313a03429fe1 100644 --- a/docs/src/modules/components/PageTitle.js +++ b/docs/src/modules/components/PageTitle.js @@ -5,18 +5,15 @@ import PageContext from 'docs/src/modules/components/PageContext'; // TODO: it really wants to be named useTitle but we're not quite there yet. function PageTitle(props) { - return ( - - {({ activePage }) => { - if (!activePage) { - throw new Error('Missing activePage.'); - } + const { activePage } = React.useContext(PageContext); - const title = activePage.title !== false ? pageToTitle(activePage) : null; - return props.children(title); - }} - - ); + if (!activePage) { + throw new Error('Missing activePage.'); + } + + const title = activePage.title !== false ? pageToTitle(activePage) : null; + + return props.children(title); } PageTitle.propTypes = { diff --git a/docs/src/pages/css-in-js/api/api.md b/docs/src/pages/css-in-js/api/api.md index baac7de8e15c7e..613e4c19e43d32 100644 --- a/docs/src/pages/css-in-js/api/api.md +++ b/docs/src/pages/css-in-js/api/api.md @@ -221,8 +221,9 @@ This `classes` object contains the name of the class names injected in the DOM. Some implementation details that might be interesting to being aware of: - It adds a `classes` property so you can override the injected class names from the outside. -- It adds an `innerRef` property so you can get a reference to the wrapped component. The usage of `innerRef` is identical to `ref`. -- It forwards *non React static* properties so this HOC is more "transparent". +- It forwards refs to the inner component. +- The `innerRef` prop is deprecated. Use `ref` instead. +- It does **not** copy over statics. For instance, it can be used to defined a `getInitialProps()` static method (next.js). #### Arguments @@ -296,7 +297,7 @@ in the render method. #### Returns -`Component`: The new component created. +`Component`: The new component created. Does forward refs to the inner component. #### Examples diff --git a/packages/material-ui-styles/src/RefHolder.js b/packages/material-ui-styles/src/RefHolder.js deleted file mode 100644 index 35c871d4d4ff35..00000000000000 --- a/packages/material-ui-styles/src/RefHolder.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -// This is a temporary component, we should be able to remove it once all our components -// implement forwardRef. -class RefHolder extends React.Component { - render() { - return this.props.children; - } -} - -RefHolder.propTypes = { - children: PropTypes.node, -}; - -export default RefHolder; diff --git a/packages/material-ui-styles/src/install.js b/packages/material-ui-styles/src/install.js index 74e33d28722d54..7588080e7cc5c2 100644 --- a/packages/material-ui-styles/src/install.js +++ b/packages/material-ui-styles/src/install.js @@ -1,4 +1,3 @@ -/* istanbul ignore file */ /* eslint-disable no-underscore-dangle */ import { ponyfillGlobal } from '@material-ui/utils'; diff --git a/packages/material-ui-styles/src/withStyles.js b/packages/material-ui-styles/src/withStyles.js index 8268e97f34ac6a..1910dc50d7d234 100644 --- a/packages/material-ui-styles/src/withStyles.js +++ b/packages/material-ui-styles/src/withStyles.js @@ -2,9 +2,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import warning from 'warning'; import hoistNonReactStatics from 'hoist-non-react-statics'; -import { getDisplayName } from '@material-ui/utils'; +import { chainPropTypes, getDisplayName } from '@material-ui/utils'; import makeStyles from './makeStyles'; -import RefHolder from './RefHolder'; import getThemeProps from './getThemeProps'; import useTheme from './useTheme'; @@ -67,11 +66,7 @@ const withStyles = (stylesOrCreator, options = {}) => Component => { } } - return ( - - - - ); + return ; }); WithStyles.propTypes = { @@ -80,9 +75,20 @@ const withStyles = (stylesOrCreator, options = {}) => Component => { */ classes: PropTypes.object, /** + * @deprecated * Use that property to pass a ref callback to the decorated component. */ - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + innerRef: chainPropTypes(PropTypes.oneOfType([PropTypes.func, PropTypes.object]), props => { + if (props.innerRef == null) { + return null; + } + + return null; + // return new Error( + // 'Material-UI: The `innerRef` prop is deprecated and will be removed in v5. ' + + // 'Refs are now automatically forwarded to the inner component.', + // ); + }), }; if (process.env.NODE_ENV !== 'production') { diff --git a/packages/material-ui-styles/src/withStyles.test.js b/packages/material-ui-styles/src/withStyles.test.js index 6a06769129ce19..3a7c80eb765676 100644 --- a/packages/material-ui-styles/src/withStyles.test.js +++ b/packages/material-ui-styles/src/withStyles.test.js @@ -7,6 +7,7 @@ import { Input } from '@material-ui/core'; import { createMount } from '@material-ui/core/test-utils'; import { isMuiElement } from '@material-ui/core/utils/reactHelpers'; import createMuiTheme from '@material-ui/core/styles/createMuiTheme'; +// import consoleErrorMock from 'test/utils/consoleErrorMock'; import StylesProvider from './StylesProvider'; import ThemeProvider from './ThemeProvider'; import withStyles from './withStyles'; @@ -38,6 +39,68 @@ describe('withStyles', () => { assert.strictEqual(isMuiElement(, ['Input']), true); }); + describe('refs', () => { + it('forwards ref to class components', () => { + // eslint-disable-next-line react/prefer-stateless-function + class TargetComponent extends React.Component { + render() { + return null; + } + } + const StyledTarget = withStyles({})(TargetComponent); + + const ref = React.createRef(); + mount( + + + , + ); + assert.instanceOf(ref.current, TargetComponent); + }); + + it('forwards refs to React.forwardRef types', () => { + const StyledTarget = withStyles({})( + // eslint-disable-next-line react/no-multi-comp + React.forwardRef((props, ref) =>
), + ); + + const ref = React.createRef(); + mount( + + + , + ); + assert.strictEqual(ref.current.nodeName, 'DIV'); + }); + + // describe('innerRef', () => { + // beforeEach(() => { + // consoleErrorMock.spy(); + // }); + + // afterEach(() => { + // consoleErrorMock.reset(); + // PropTypes.resetWarningCache(); + // }); + + // it('is deprecated', () => { + // const ThemedDiv = withStyles({})('div'); + + // mount( + // + // + // , + // ); + + // assert.strictEqual(consoleErrorMock.callCount(), 1); + // assert.include( + // consoleErrorMock.args()[0][0], + // 'Warning: Failed prop type: Material-UI: The `innerRef` prop is deprecated', + // ); + // }); + // }); + }); + it('should forward the properties', () => { const Test = props =>
{props.foo}
; Test.propTypes = { diff --git a/packages/material-ui-styles/src/withTheme.js b/packages/material-ui-styles/src/withTheme.js index 1ea9cdb26b0211..0baa514f965d2c 100644 --- a/packages/material-ui-styles/src/withTheme.js +++ b/packages/material-ui-styles/src/withTheme.js @@ -1,9 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import hoistNonReactStatics from 'hoist-non-react-statics'; -import { getDisplayName } from '@material-ui/utils'; +import { chainPropTypes, getDisplayName } from '@material-ui/utils'; import useTheme from './useTheme'; -import RefHolder from './RefHolder'; // Provide the theme object as a property to the input component. // It's an alternative API to useTheme(). @@ -21,18 +20,24 @@ const withTheme = Component => { const WithTheme = React.forwardRef(function WithTheme(props, ref) { const { innerRef, ...other } = props; const theme = useTheme(); - return ( - - - - ); + return ; }); WithTheme.propTypes = { /** + * @deprecated * Use that property to pass a ref callback to the decorated component. */ - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + innerRef: chainPropTypes(PropTypes.oneOfType([PropTypes.func, PropTypes.object]), props => { + if (props.innerRef == null) { + return null; + } + + return new Error( + 'Material-UI: The `innerRef` prop is deprecated and will be removed in v5. ' + + 'Refs are now automatically forwarded to the inner component.', + ); + }), }; if (process.env.NODE_ENV !== 'production') { diff --git a/packages/material-ui-styles/src/withTheme.test.js b/packages/material-ui-styles/src/withTheme.test.js index da475610632cb3..5018b3477c3d01 100644 --- a/packages/material-ui-styles/src/withTheme.test.js +++ b/packages/material-ui-styles/src/withTheme.test.js @@ -1,9 +1,11 @@ +/* eslint-disable react/no-multi-comp */ import React from 'react'; import { assert } from 'chai'; import { createMount } from '@material-ui/core/test-utils'; import { Input } from '@material-ui/core'; import { isMuiElement } from '@material-ui/core/utils/reactHelpers'; import PropTypes from 'prop-types'; +import consoleErrorMock from 'test/utils/consoleErrorMock'; import withTheme from './withTheme'; import ThemeProvider from './ThemeProvider'; @@ -52,6 +54,69 @@ describe('withTheme', () => { assert.strictEqual(isMuiElement(, ['Input']), true); }); + describe('refs', () => { + it('forwards ref to class components', () => { + // eslint-disable-next-line react/prefer-stateless-function + class TargetComponent extends React.Component { + render() { + return null; + } + } + const ThemedTarget = withTheme(TargetComponent); + + const ref = React.createRef(); + mount( + + + , + ); + + assert.instanceOf(ref.current, TargetComponent); + }); + + it('forwards refs to React.forwardRef types', () => { + const ThemedTarget = withTheme( + React.forwardRef((props, ref) =>
), + ); + + const ref = React.createRef(); + mount( + + + , + ); + + assert.strictEqual(ref.current.nodeName, 'DIV'); + }); + + describe('innerRef', () => { + beforeEach(() => { + consoleErrorMock.spy(); + }); + + afterEach(() => { + consoleErrorMock.reset(); + PropTypes.resetWarningCache(); + }); + + it('is deprecated', () => { + const ThemedDiv = withTheme('div'); + + mount( + + + , + ); + + assert.strictEqual(consoleErrorMock.callCount(), 1); + assert.include( + consoleErrorMock.args()[0][0], + 'Warning: Failed prop type: Material-UI: The `innerRef` prop is deprecated', + ); + }); + }); + }); + it('should throw is the import is invalid', () => { assert.throw( () => withTheme(undefined), diff --git a/packages/material-ui-utils/src/chainPropTypes.js b/packages/material-ui-utils/src/chainPropTypes.js index 4b3991e15728b2..d5e965bcf04ce5 100644 --- a/packages/material-ui-utils/src/chainPropTypes.js +++ b/packages/material-ui-utils/src/chainPropTypes.js @@ -1,5 +1,4 @@ function chainPropTypes(propType1, propType2) { - /* istanbul ignore if */ if (process.env.NODE_ENV === 'production') { return () => null; } diff --git a/packages/material-ui-utils/src/exactProp.js b/packages/material-ui-utils/src/exactProp.js index 151de036215cb7..c4d223f7c4fa38 100644 --- a/packages/material-ui-utils/src/exactProp.js +++ b/packages/material-ui-utils/src/exactProp.js @@ -6,7 +6,6 @@ export const specialProperty = 'exact-prop: \u200b'; function exactProp(propTypes) { - /* istanbul ignore if */ if (process.env.NODE_ENV === 'production') { return propTypes; } diff --git a/packages/material-ui/src/ButtonBase/createRippleHandler.js b/packages/material-ui/src/ButtonBase/createRippleHandler.js index 71001ad8d3c23c..3b18cdc0201c71 100644 --- a/packages/material-ui/src/ButtonBase/createRippleHandler.js +++ b/packages/material-ui/src/ButtonBase/createRippleHandler.js @@ -27,7 +27,6 @@ let createRippleHandler = (instance, eventName, action, cb) => event => { return true; }; -/* istanbul ignore if */ if (typeof window === 'undefined') { createRippleHandler = () => () => {}; } diff --git a/packages/material-ui/src/CircularProgress/CircularProgress.js b/packages/material-ui/src/CircularProgress/CircularProgress.js index 0a5809b70a2bbf..30785220e59909 100644 --- a/packages/material-ui/src/CircularProgress/CircularProgress.js +++ b/packages/material-ui/src/CircularProgress/CircularProgress.js @@ -191,7 +191,6 @@ CircularProgress.propTypes = { * This only works if variant is `indeterminate`. */ disableShrink: chainPropTypes(PropTypes.bool, props => { - /* istanbul ignore if */ if (props.disableShrink && props.variant !== 'indeterminate') { return new Error( 'Material-UI: you have provided the `disableShrink` property ' + diff --git a/packages/material-ui/src/Divider/Divider.js b/packages/material-ui/src/Divider/Divider.js index 20ab6841e278d2..ba7a3b71d47604 100644 --- a/packages/material-ui/src/Divider/Divider.js +++ b/packages/material-ui/src/Divider/Divider.js @@ -91,7 +91,6 @@ Divider.propTypes = { * Instead use `variant="inset"`. */ inset: chainPropTypes(PropTypes.bool, props => { - /* istanbul ignore if */ if (props.inset) { return new Error( 'Material-UI: you are using the deprecated `inset` property ' + diff --git a/packages/material-ui/src/ExpansionPanel/ExpansionPanel.js b/packages/material-ui/src/ExpansionPanel/ExpansionPanel.js index c3a570d8b3710b..9ee6cc1519619b 100644 --- a/packages/material-ui/src/ExpansionPanel/ExpansionPanel.js +++ b/packages/material-ui/src/ExpansionPanel/ExpansionPanel.js @@ -162,7 +162,6 @@ ExpansionPanel.propTypes = { ); } - /* istanbul ignore if */ if (!React.isValidElement(summary)) { return new Error( `Material-UI: Expected the first child of ExpansionPanel to be a valid element.${ diff --git a/packages/material-ui/src/styles/MuiThemeProvider.js b/packages/material-ui/src/styles/MuiThemeProvider.js index a03a6d32846a25..a9fcc180c1ce9a 100644 --- a/packages/material-ui/src/styles/MuiThemeProvider.js +++ b/packages/material-ui/src/styles/MuiThemeProvider.js @@ -153,7 +153,6 @@ MuiThemeProviderOld.contextTypes = { muiThemeProviderOptions: PropTypes.object, }; -/* istanbul ignore if */ if (!ponyfillGlobal.__MUI_STYLES__) { ponyfillGlobal.__MUI_STYLES__ = {}; } diff --git a/packages/material-ui/src/styles/withStyles.js b/packages/material-ui/src/styles/withStyles.js index e75f29a8be79af..06a80ecb578398 100644 --- a/packages/material-ui/src/styles/withStyles.js +++ b/packages/material-ui/src/styles/withStyles.js @@ -316,7 +316,6 @@ const withStylesOld = (stylesOrCreator, options = {}) => Component => { return WithStyles; }; -/* istanbul ignore if */ if (!ponyfillGlobal.__MUI_STYLES__) { ponyfillGlobal.__MUI_STYLES__ = {}; } diff --git a/packages/material-ui/src/styles/withTheme.js b/packages/material-ui/src/styles/withTheme.js index 534176fef5b18f..28e22b0d24b526 100644 --- a/packages/material-ui/src/styles/withTheme.js +++ b/packages/material-ui/src/styles/withTheme.js @@ -20,7 +20,6 @@ function getDefaultTheme() { // Provide the theme object as a property to the input component. const withThemeOld = Component => { - /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && Component === undefined) { throw new Error( [ @@ -80,7 +79,6 @@ const withThemeOld = Component => { return WithTheme; }; -/* istanbul ignore if */ if (!ponyfillGlobal.__MUI_STYLES__) { ponyfillGlobal.__MUI_STYLES__ = {}; } diff --git a/packages/material-ui/src/test-utils/testRef.js b/packages/material-ui/src/test-utils/testRef.js index 8cbb338801928f..72773e254a2993 100644 --- a/packages/material-ui/src/test-utils/testRef.js +++ b/packages/material-ui/src/test-utils/testRef.js @@ -16,6 +16,6 @@ function assertDOMNode(node) { */ export default function testRef(element, mount, onRef = assertDOMNode) { const ref = React.createRef(); - mount(<>{React.cloneElement(element, { innerRef: ref })}); + mount({React.cloneElement(element, { innerRef: ref })}); onRef(ref.current); } diff --git a/packages/material-ui/src/utils/deprecatedPropType.js b/packages/material-ui/src/utils/deprecatedPropType.js index 01eef8ca8d7299..1c45af1e5ae626 100644 --- a/packages/material-ui/src/utils/deprecatedPropType.js +++ b/packages/material-ui/src/utils/deprecatedPropType.js @@ -1,5 +1,4 @@ function deprecatedPropType(validator, reason) { - /* istanbul ignore if */ if (process.env.NODE_ENV === 'production') { return () => null; } diff --git a/packages/material-ui/src/utils/requirePropFactory.js b/packages/material-ui/src/utils/requirePropFactory.js index d5fd9e14d55cbb..4c5174ade9e437 100644 --- a/packages/material-ui/src/utils/requirePropFactory.js +++ b/packages/material-ui/src/utils/requirePropFactory.js @@ -1,5 +1,4 @@ function requirePropFactory(componentNameInError) { - /* istanbul ignore if */ if (process.env.NODE_ENV === 'production') { return () => null; } diff --git a/packages/material-ui/src/utils/unsupportedProp.js b/packages/material-ui/src/utils/unsupportedProp.js index f978ceb11c4334..a7b3776de1e9e0 100644 --- a/packages/material-ui/src/utils/unsupportedProp.js +++ b/packages/material-ui/src/utils/unsupportedProp.js @@ -1,5 +1,4 @@ function unsupportedProp(props, propName, componentName, location, propFullName) { - /* istanbul ignore if */ if (process.env.NODE_ENV === 'production') { return null; }