Repository: mui/material-ui (TypeScript)
Date: 2021-08-30 12:03:20+00:00
Task type: Bug Fix

Issue:
BaseInput ref is undefined when using with Select
<!-- Provide a general summary of the issue in the Title above -->
I'm using Material-UI Select component, with Material-UI InputBase to show the values.

I need access for the ref of the ROOT of the Input component, but it's not working and the current of the ref is undefined.
<!--
  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] -->

- [ v ] The issue is present in the latest release.
- [ v  ] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Current Behavior 😯

the ref.current of the InputBase component is undefined, when used with the Select component. it's is undefined inside the component's events. e.g: onClick.

## Expected Behavior 🤔

it should hold the reference to the root element of the InputBase element, the wrapping div around the native-input.

## Steps to Reproduce 🕹

https://codesandbox.io/s/material-demo-forked-olbo2?file=/demo.tsx
Steps:

1. Add Select
2. Add BaseInput component to the input prop of the Select component.
3. Add `const inputRef = useRef()` and attach the ref to the InputBase ref prop.
4. Add `onClick` prop to the InputBase that prints to the console the `inputRef.current`.

## Context 🔦

The menu should toggle when the InputBase is clicked. the issue is that there is elements (Tags) showing the selected values, and they has onClick event. so i need to find a way to stop the event bubbling and allow it when clicking the input.

## Your Environment 🌎
  
```
  Browser: Chrome
    System:
    OS: macOS 11.4
  Binaries:
    Node: 14.17.0 - /usr/local/bin/node
    Yarn: 1.22.10 - /usr/local/bin/yarn
    npm: 6.14.13 - /usr/local/bin/npm
  Browsers:
    Chrome: 92.0.4515.131
    Edge: Not Found
    Firefox: Not Found
    Safari: 14.1.1
  npmPackages:
    @emotion/styled:  10.0.27 
    @material-ui/core: ^4.12.1 => 4.12.1 
    @material-ui/styles:  4.11.4 
    @material-ui/system:  4.12.1 
    @material-ui/types:  5.1.0 
    @material-ui/utils:  4.11.2 
    @types/react: ^17.0.0 => 17.0.1 
    react: ^17.0.1 => 17.0.1 
    react-dom: ^17.0.1 => 17.0.1 
    styled-components: ^5.2.1 => 5.2.1 
    typescript: ^4.1.3 => 4.1.3 
```
</details>



Issue discussion:
Confirmed for 5.x: https://codesandbox.io/s/material-demo-forked-cx22l?file=/demo.tsx
Hi, I want to work on it.
Using the component Input and InputBase has the prop inputRef that passes the ref to the input, so using it rather than ref work on the example provided by the Itaytur. `inputRef={inputRef}`
Showing the value using .log inputRef shows the correct value, and using inputRef.current show the previous value selected.

This props inputRef is used to handle the [Special Props Warning](https://reactjs.org/warnings/special-props.html)?

With the example provided by eps1lon that uses a standard input element of the HTML, I still working on it.
The component Select returns a cloneElement of the input passed, and an object with the new config. 

The [cloneElement](https://reactjs.org/docs/react-api.html#cloneelement): "key and ref from the original element will be preserved if no key and ref present in the config."

And the Select provide the ref to the cloneElement in the config, so when we pass the ref to the input and not to Select, the Select override the ref of the input with undefined. 


Solution patch:
diff --git a/packages/material-ui/src/Select/Select.js b/packages/material-ui/src/Select/Select.js
--- a/packages/material-ui/src/Select/Select.js
+++ b/packages/material-ui/src/Select/Select.js
@@ -12,6 +12,7 @@ import NativeSelectInput from '../NativeSelect/NativeSelectInput';
 import FilledInput from '../FilledInput';
 import OutlinedInput from '../OutlinedInput';
 import useThemeProps from '../styles/useThemeProps';
+import useForkRef from '../utils/useForkRef';
 import { getSelectUtilityClasses } from './selectClasses';
 
 const useUtilityClasses = (ownerState) => {
@@ -73,6 +74,8 @@ const Select = React.forwardRef(function Select(inProps, ref) {
   const classes = useUtilityClasses(ownerState);
   const { root, ...otherClasses } = classesProp;
 
+  const inputComponentRef = useForkRef(ref, InputComponent.ref);
+
   return React.cloneElement(InputComponent, {
     // Most of the logic is implemented in `SelectInput`.
     // The `Select` component is a simple API wrapper to expose something better to play with.
@@ -101,7 +104,7 @@ const Select = React.forwardRef(function Select(inProps, ref) {
       ...(input ? input.props.inputProps : {}),
     },
     ...(multiple && native && variant === 'outlined' ? { notched: true } : {}),
-    ref,
+    ref: inputComponentRef,
     className: clsx(classes.root, InputComponent.props.className, className),
     ...other,
   });


Test patch:
diff --git a/packages/material-ui/src/Select/Select.test.js b/packages/material-ui/src/Select/Select.test.js
--- a/packages/material-ui/src/Select/Select.test.js
+++ b/packages/material-ui/src/Select/Select.test.js
@@ -1195,11 +1195,37 @@ describe('<Select />', () => {
     );
   });
 
-  it('should merge the class names', () => {
-    const { getByTestId } = render(
-      <Select className="foo" input={<InputBase data-testid="root" className="bar" />} value="" />,
-    );
-    expect(getByTestId('root')).to.have.class('foo');
-    expect(getByTestId('root')).to.have.class('bar');
+  describe('prop: input', () => {
+    it('merges `ref` of `Select` and `input`', () => {
+      const Input = React.forwardRef(function Input(props, ref) {
+        const { inputProps, inputComponent: Component, ...other } = props;
+
+        React.useImperativeHandle(ref, () => {
+          return { refToInput: true };
+        });
+
+        return <Component {...inputProps} {...other} ref={ref} />;
+      });
+      const inputRef = React.createRef();
+      const selectRef = React.createRef();
+      render(
+        <Select input={<Input data-testid="input" ref={inputRef} value="" />} ref={selectRef} />,
+      );
+
+      expect(inputRef).to.deep.equal({ current: { refToInput: true } });
+      expect(selectRef).to.deep.equal({ current: { refToInput: true } });
+    });
+
+    it('should merge the class names', () => {
+      const { getByTestId } = render(
+        <Select
+          className="foo"
+          input={<InputBase data-testid="root" className="bar" />}
+          value=""
+        />,
+      );
+      expect(getByTestId('root')).to.have.class('foo');
+      expect(getByTestId('root')).to.have.class('bar');
+    });
   });
 });