'material-ui v4.0.1 warning "Expected an element that can hold a ref"

I upgraded to material-ui v4.0.1 and I see that Modals now require a forwarded ref. I'm having some trouble implementing a fix for this using class components and Dialogs.

I tried using React.createRef() as well as React.forwardRef((props, ref) => (...) in a few places but I can't figure out how to resolve this warning.

In my parent component I render a custom component

<ApolloFormDialog />

In ApolloFormDialog I render essentially:

<Dialog ...>
  {title}
  {subtitle}
  {form}
</Dialog>

The full warning is Warning: Failed prop type: Invalid prop 'children' supplied to 'Modal'. Expected an element that can hold a ref. Did you accidentally use a plain function component for an element instead?

However I am using class components currently. Migrating to use function components is not an option right now as my app is rather large.


I have tried adding a ref to ApolloFormDialog as

<ApolloFormDialog ref={React.createRef()} />

as well as wrapping ApolloFormDialog's class with:

export default React.forwardRef((props, ref) => <ApolloFormDialog ref={ref} {...props}/>)

and then adding that ref to the dialog as

<Dialog ref={this.props.ref} />

but the warning still persists, and I'm not sure where to go from here.



Solution 1:[1]

My issue didn't actually have to do with Dialog, but with the prop TransitionComponent on Dialog.

I switch between two types of transitions in my ApolloFormDialog depending on if the screen is below a certain breakpoint, which was being called as function components:

<Dialog
  open={open}
  onClose={onRequestClose}
  classes={{
    paper: classnames(classes.dialogWidth, classes.overflowVisible),
  }}
  fullScreen={fullScreen}
  TransitionComponent={
    fullScreen ? FullscreenTransition : DefaultTransition
  }
>
  {content}
</Dialog>

FullscreenTransition and DefaultTransition come from a file and are defined as follows:

import React from 'react'
import Fade from '@material-ui/core/Fade'
import Slide from '@material-ui/core/Slide'

export function DefaultTransition(props) {
  return <Fade {...props} />
}

export function FullscreenTransition(props) {
  return <Slide direction='left' {...props} />
}

export function FullscreenExpansion(props) {
  return <Slide direction='right' {...props} />
}

Changing these functions to the following fixed my issue:

import React from 'react'
import Fade from '@material-ui/core/Fade'
import Slide from '@material-ui/core/Slide'

export const DefaultTransition = React.forwardRef((props, ref) => (
  <Fade {...props} ref={ref} />
))

export const FullscreenTransition = React.forwardRef((props, ref) => (
  <Slide direction='left' {...props} ref={ref} />
))

export const FullscreenExpansion = React.forwardRef((props, ref) => (
  <Slide direction='right' {...props} ref={ref} />
))


This was a relatively hard issue to solve on my end, so I'm going to leave this question up just in case someone else runs into a similar issue somewhere down the road.

Solution 2:[2]

I have had the same problem with "@material-ui/core/Tooltip" wrapping a new functional component. Even if the component was wrapped in a div inside its own code.

<!-- "Did you accidentally use a plain function component for an element instead?" -->

<Tooltip>
  <NewFunctionalComponent />
</Tooltip>

<!-- Wrapped in a new div, devtools won't complain anymore -->

<Tooltip>
  <div>
    <NewFunctionalComponent />
  </div>
</Tooltip>

<!-- No more warnings! -->

Solution 3:[3]

Wrapping my component to another div inside material-ui transition componet (like Slide, Fade etc) solves my issue. Example code:

From

<Slide direction='Right' in = {isSideNavOpen} mountOnEnter unmountOnExit>
      <MyComponent />
</Slide>

To

<Slide direction='Right' in = {isSideNavOpen} mountOnEnter unmountOnExit>
     <div><MyComponent /></div>
</Slide>`

Solution 4:[4]

React.forwardRef fixes the issue for me, too.

To ensure that the React.forwardRef solution works with Typescript, you must implement the following as per the documentation in Material-UI:

const Transition = React.forwardRef<unknown, TransitionProps>(function Transition(props, ref) {
  return <Slide ref={ref} {...props} />;
});

See the source code in their demo here.

Solution 5:[5]

I was getting the same error

expected an element that cand hold a ref

I solved by adding a <Box></Box> surrounding my children component.

From

<Modal open={open} onClose={handleClose}>
    <DetailsCard />
</Modal>

To

<Modal open={open} onClose={handleClose}>
  <Box>
    <DetailsCard />
  </Box>
</Modal>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Lief
Solution 2
Solution 3 Arghya Sadhu
Solution 4 PacificFrost
Solution 5 Eduardo Pedra de Oliveira