'Conditionally return ForwardRefExoticComponent and FunctionalComponent

I have created a wrapper on MUI TextField component i.e. TextFieldWrapper with custom stylings and validations. It is being in the app in many places.

The problem with this is it cannot be used with MUI Tooltip because for that ref needs to be forwarded.

I cannot wrap the TextFieldWrapper with React.forwardRef() because wrapping it with forwardRef will change its return type and it breaks at other places.

So, currently, I have 2 TextField components i.e. TextFieldWrapper and TooltipTextField.

The issue is, as I said earlier, the TextField has some stylings and validations applied to it and that is being duplicated in both TextFieldWrapper and TooltipTextField if I use separate components as shown below.

Is there a workaround for this or it could be improved in some way? I mean if I could combine these 2 (TextFieldWrapper and TooltipTextField) into one functional component and return it conditionally or in some way?

Code Sandbox: https://codesandbox.io/s/basictooltip-material-demo-forked-v8lrb4?file=/demo.tsx:0-799

import * as React from "react";
import { Tooltip, TextField, TextFieldProps } from "@mui/material";

// Return type is React.ForwardRefExoticComponent<Pick<TextFieldProps> & React.RefAttributes<...>>
const TooltipTextField = React.forwardRef(
  (
    props: TextFieldProps,
    ref: React.RefObject<HTMLInputElement>
  ): React.ReactElement => {
    return <TextField ref={ref} {...props} />;
  }
);

// Return type is React.React.ReactElement
const TextFieldWrapper = (props: TextFieldProps): React.ReactElement => {
  return <TextField {...props} />;
};

export default function BasicTooltip() {
  return (
    <>
      <Tooltip title="Tooltip Textfield">
        <TooltipTextField value="Tooltip TextField" />
      </Tooltip>

      <TextFieldWrapper value="General TextField" />
    </>
  );
}


Solution 1:[1]

There are three ways you can do.

  1. Using assertion as keyword and specify the type of the component you have.
const TooltipTextField = React.forwardRef(
  (props: TextFieldProps, ref: React.RefObject<HTMLInputElement>) => {
    return <TextField ref={ref} {...props} />;
  }
) as React.FC<TextFieldProps>;
  1. Wrap the component with another MUI component like Box. This will change the target component to Box and it will do what is needed for Tooltip.
<Tooltip title="Tooltip Textfield">
  <Box sx={{ display: "inline-block" }}>
    <GeneralTextField value="General TextField" />
  </Box>
</Tooltip>
  1. Create a component combining Tooltip and TextField (what you mentioned).
const TextFieldWithTooltip = ({ tooltipProps, ...rest }: TextFieldProps & { tooltipProps?: TooltipProps }): React.ReactElement => {
  return (
    <Tooltip {...tooltipProps} title={tooltipProps?.title || ''}>
      <TextField {...rest} />
    </Tooltip>
  );
}

Please take a look at tooltip props at https://mui.com/material-ui/api/tooltip/#props

And notice that,

  • If you pass empty string for title wont show the tooltip.
  • You also can use props like disableHoverListener which prevents showing the tooltip, etc.

I also provided a demo on Codesandbox which may be helpful. Follow FIXME tags in the code ;)

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 Adel Armand