Andru
Andru

Reputation: 6204

Why does custom input component cause "Function components cannot be given refs" warning?

While trying to customize the input component via MUI's InputUnstyled component (or any other unstyled component, e.g. SwitchUnstyled, SelectUnstyled etc.), I get the warning

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Check the render method of `ForwardRef`.
InputElement@http://localhost:3000/main.4c2d885b9953394bb5ec.hot-update.js:59:45
div
...

I use the components prop to define a custom Input element in my own MyStyledInput component which wraps MUIs InputUnstyled:

import InputUnstyled, {
    InputUnstyledProps
} from '@mui/base/InputUnstyled';

const MyStyledInput: React.FC<InputUnstyledProps> = props => {
    const { components, ...otherProps } = props;

    return (
        <InputUnstyled
            components={{
                Input: InputElement,
                ...components,
            }}
            {...otherProps}
        />
    );
};

My custom input component InputElement which is causing the Function components cannot be given refs warning:

import {
    InputUnstyledInputSlotProps,
} from '@mui/base/InputUnstyled';
import { Box, BoxProps } from '@mui/material';

const InputElement: React.FC<BoxProps & InputUnstyledInputSlotProps> = (
    { ownerState, ...props }
) => {
    return (
        <Box
            component="input"
            // any style customizations
            {...props}
            ref={ref}
        />
    );
});

Note: I'm using component="input to make MUI's Box component not render an HTML div but an HTML input component in the DOM.

Why am I getting this warning?

Other related questions here, here and here address similar issues but don't work with MUI Unstyled components. These threads also don't explain why

Upvotes: 1

Views: 1057

Answers (1)

Andru
Andru

Reputation: 6204

The warning wants you to have a look at the InputElement component. To be honest, the stack-trace is a bit misleading here. It says:

Check the render method of ForwardRef. InputElement@http://localhost:3000/main.4c2d885b9953394bb5ec.hot-update.js:59:45 div

You can ignore the ForwardRef here. Internally InputElement is wrapped by

The crucial part for understanding this warning is:

Function components cannot be given refs. Attempts to access this ref will fail.

That is, if someone tries to access the actual HTML input element in the DOM via a ref (which Material UI actually tries to do), it will not succeed because the functional component InputElement is not able to pass that ref on to the input element (here created via a MUI Box component).

Hence, the warning continues with:

Did you mean to use React.forwardRef()?

This proposes the solution to wrap your function component with React.forwardRef. forwardRef gives you the possibility to get hold of the ref and pass it on to the actual input component (which in this case is the Box component with the prop component="input"). It should look as such:

import {
    InputUnstyledInputSlotProps,
} from '@mui/base/InputUnstyled';
import { Box, BoxProps } from '@mui/material';

const InputElement = React.forwardRef<
    HTMLInputElement,
    BoxProps & InputUnstyledInputSlotProps
>(({ ownerState, ...props }, ref) => {
    const theme = useTheme();

    return (
        <Box
            component="input"
            // any style customizations
            {...props}
            ref={ref}
        />
    );
});

Why do I have to deal with the ref in the first place?

In case of an HTML input element, there as a high probability you want to access it's DOM node via a ref. This is the case if you use your React input component as an uncontrolled component. An uncontrolled input component holds its state (i.e. whatever the user enters in that input field) in the actual DOM node and not inside of the state value of a React.useState hook. If you control the input value via a React.useState hook, you're using the input as a controlled component.

Note: An input with type="file" is always an uncontrolled component. This is explained in the React docs section about the file input tag.

Upvotes: 1

Related Questions