Repository: mui/material-ui (TypeScript)
Date: 2018-07-22 15:42:00+00:00
Task type: Feature

Issue:
[withWidth] Add innerRef property
<!--- Provide a general summary of the feature in the Title above -->

<!--
    Thank you very much for contributing to Material-UI by creating an issue! ❤️
    To avoid duplicate issues we ask you to check off the following list.
-->

<!-- Checked checkbox should look like this: [x] -->
- [x] This is a v1.x issue. <!-- (v0.x is no longer maintained) -->
- [x] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Expected Behavior
<!---
    Describe how it should work.
-->
I expected to use like withStyles, that I access with innerRef.

## Current Behavior
<!---
    Explain the difference from current behavior.
-->

I can't access the ref of my component.

## Examples
<!---
    Provide a link to the Material design specification, other implementations, 
    or screenshots of the expected behavior.
-->

```js
export default withWidth()(MyComponent)
```

## Context
<!---
    What are you trying to accomplish? How has the lack of this feature affected you?
    Providing context helps us come up with a solution that is most useful in the real world.
-->
I want to access refs using innerRef or something like that when using HOC withWidth


Issue discussion:
@itelo You can use the [RootRef](https://material-ui.com/api/root-ref/) component for this use case. Regarding the `innerRef` property. My hope is that it can be legacy with #10825.
The `withTheme` higher-order component could take advantage of it too.
@oliviertassinari can you give an example of how to use the RootRef? I want to access the props and state. With RootRef I only get access to the DOM. 
> I want to access the props and state.

@itelo No, you can't access the state with RootRef, you will only be able to do it with `forwardRef` (#10825).
Anyway, I think that we should be **consistent**, have `withWidth`, `withTheme` and `withStyles` support the same properties. I this something you want to work on? :)
Certainly, tomorrow I will submit a PR :)
@itelo Awesome! We can copy the withStyles implementation:
https://github.com/mui-org/material-ui/blob/267beda0067f2d1dbefb4c90b9e724c9be50cbd6/packages/material-ui/src/styles/withStyles.js#L296-L299

Solution patch:
diff --git a/packages/material-ui/src/styles/withTheme.d.ts b/packages/material-ui/src/styles/withTheme.d.ts
--- a/packages/material-ui/src/styles/withTheme.d.ts
+++ b/packages/material-ui/src/styles/withTheme.d.ts
@@ -3,6 +3,7 @@ import { ConsistentWith } from '..';
 
 export interface WithTheme {
   theme: Theme;
+  innerRef?: React.Ref<any> | React.RefObject<any>;
 }
 
 declare const withTheme: () => <P extends ConsistentWith<P, WithTheme>>(
diff --git a/packages/material-ui/src/styles/withTheme.js b/packages/material-ui/src/styles/withTheme.js
--- a/packages/material-ui/src/styles/withTheme.js
+++ b/packages/material-ui/src/styles/withTheme.js
@@ -1,4 +1,5 @@
 import React from 'react';
+import PropTypes from 'prop-types';
 import hoistNonReactStatics from 'hoist-non-react-statics';
 import wrapDisplayName from 'recompose/wrapDisplayName';
 import createMuiTheme from './createMuiTheme';
@@ -44,10 +45,18 @@ const withTheme = () => Component => {
     }
 
     render() {
-      return <Component theme={this.state.theme} {...this.props} />;
+      const { innerRef, ...other } = this.props;
+      return <Component theme={this.state.theme} ref={innerRef} {...other} />;
     }
   }
 
+  WithTheme.propTypes = {
+    /**
+     * Use that property to pass a ref callback to the decorated component.
+     */
+    innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
+  };
+
   WithTheme.contextTypes = themeListener.contextTypes;
 
   if (process.env.NODE_ENV !== 'production') {
diff --git a/packages/material-ui/src/withWidth/withWidth.d.ts b/packages/material-ui/src/withWidth/withWidth.d.ts
--- a/packages/material-ui/src/withWidth/withWidth.d.ts
+++ b/packages/material-ui/src/withWidth/withWidth.d.ts
@@ -7,6 +7,7 @@ export interface WithWidthOptions {
 
 export interface WithWidthProps {
   width: Breakpoint;
+  innerRef?: React.Ref<any> | React.RefObject<any>;
 }
 
 export function isWidthDown(
diff --git a/packages/material-ui/src/withWidth/withWidth.js b/packages/material-ui/src/withWidth/withWidth.js
--- a/packages/material-ui/src/withWidth/withWidth.js
+++ b/packages/material-ui/src/withWidth/withWidth.js
@@ -98,7 +98,7 @@ const withWidth = (options = {}) => Component => {
     }
 
     render() {
-      const { initialWidth, theme, width, ...other } = this.props;
+      const { initialWidth, theme, width, innerRef, ...other } = this.props;
 
       const props = {
         width:
@@ -127,7 +127,7 @@ const withWidth = (options = {}) => Component => {
 
       return (
         <EventListener target="window" onResize={this.handleResize}>
-          <Component {...more} {...props} />
+          <Component {...more} {...props} ref={innerRef} />
         </EventListener>
       );
     }
@@ -144,6 +144,10 @@ const withWidth = (options = {}) => Component => {
      * http://caniuse.com/#search=client%20hint
      */
     initialWidth: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
+    /**
+     * Use that property to pass a ref callback to the decorated component.
+     */
+    innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
     /**
      * @ignore
      */


Test patch:
diff --git a/packages/material-ui/src/styles/withTheme.test.js b/packages/material-ui/src/styles/withTheme.test.js
--- a/packages/material-ui/src/styles/withTheme.test.js
+++ b/packages/material-ui/src/styles/withTheme.test.js
@@ -1,11 +1,18 @@
 import React from 'react';
 import { assert } from 'chai';
+import { spy } from 'sinon';
 import createBroadcast from 'brcast';
 import { createShallow, createMount } from '../test-utils';
 import { CHANNEL } from './themeListener';
 import withTheme from './withTheme';
 
 const Empty = () => <div />;
+// eslint-disable-next-line react/prefer-stateless-function
+class EmptyClass extends React.Component<{}> {
+  render() {
+    return <div />;
+  }
+}
 
 describe('withTheme', () => {
   let shallow;
@@ -44,4 +51,13 @@ describe('withTheme', () => {
     broadcast.setState(newTheme);
     assert.strictEqual(wrapper.instance().state.theme, newTheme);
   });
+
+  describe('prop: innerRef', () => {
+    it('should provide a ref on the inner component', () => {
+      const ThemedComponent = withTheme()(EmptyClass);
+      const handleRef = spy();
+      mount(<ThemedComponent innerRef={handleRef} />);
+      assert.strictEqual(handleRef.callCount, 1);
+    });
+  });
 });
diff --git a/packages/material-ui/src/withWidth/withWidth.test.js b/packages/material-ui/src/withWidth/withWidth.test.js
--- a/packages/material-ui/src/withWidth/withWidth.test.js
+++ b/packages/material-ui/src/withWidth/withWidth.test.js
@@ -1,12 +1,19 @@
 import React from 'react';
 import { assert } from 'chai';
-import { useFakeTimers } from 'sinon';
+import { useFakeTimers, spy } from 'sinon';
 import { createMount, createShallow } from '../test-utils';
 import withWidth, { isWidthDown, isWidthUp } from './withWidth';
 import createBreakpoints from '../styles/createBreakpoints';
 import createMuiTheme from '../styles/createMuiTheme';
 
 const Empty = () => <div />;
+// eslint-disable-next-line react/prefer-stateless-function
+class EmptyClass extends React.Component<{}> {
+  render() {
+    return <div />;
+  }
+}
+const EmptyClassWithWidth = withWidth()(EmptyClass);
 const EmptyWithWidth = withWidth()(Empty);
 
 const breakpoints = createBreakpoints({});
@@ -39,6 +46,15 @@ describe('withWidth', () => {
     });
   });
 
+  describe('prop: innerRef', () => {
+    it('should provide a ref on the inner component', () => {
+      const handleRef = spy();
+
+      mount(<EmptyClassWithWidth innerRef={handleRef} />);
+      assert.strictEqual(handleRef.callCount, 1);
+    });
+  });
+
   describe('browser', () => {
     it('should provide the right width to the child element', () => {
       const wrapper = mount(<EmptyWithWidth />);