Material-ui: [TextField] Outlined label gap is missing when first rendered hidden

Created on 8 Sep 2019  路  26Comments  路  Source: mui-org/material-ui

  • [X] The issue is present in the latest release.
  • [X] I have searched the issues of this repository and believe that this is not a duplicate.

Current Behavior 馃槸

When I place a TextField with an outline within a SwipeableViews tabbed view, the notch within the outline that provides space for the label does not appear on tabs other than the first one. The notch appears on TextFields in the initial tab.

Expected Behavior 馃

Outlined TextFields on secondary tabs should have a properly sized notch to contain the TextField's label.

Steps to Reproduce 馃暪

Steps:

  1. Place a TextField within a tab SwipeableView, with an outline.
  2. Open the page. The TextFields on the starting tab should have notches in the outline.
  3. Switch tabs. Tabs besides the initial one will have no notches in the outline.

I have reproduced this issue on a minimal CodeSandbox: https://codesandbox.io/s/mutable-butterfly-36u26

Context 馃敠

I encountered this issue attempting to place a TextField within a tabbed view.

SwipeableViews is not part of this project, but it is used alongwide Material UI in the official documentation: https://material-ui.com/components/tabs/#fixed-tabs.

Your Environment 馃寧

| Tech | Version |
| ----------- | ------- |
| Material-UI | v4.4.0 |
| React | v16.8.6 |
| Browser | Firefox 69.0, Chrome 76.0.3809.132 |

bug 馃悰 TextField

Most helpful comment

While we have a broader issue with the outlined TextField and the notch for the label, I propose that we focus on the tabs demos. I think that we can improve performance and dodge this problem with the following fix (example with one tab demo, to apply to all of them).

We can replace:
https://github.com/mui-org/material-ui/blob/5d564f9c1be5bf20b51be1a479d292bf443291ba/docs/src/pages/components/tabs/SimpleTabs.tsx#L18-L28

with

return ( 
   <Typography 
     component="div" 
     role="tabpanel" 
     hidden={value !== index} 
     id={`simple-tabpanel-${index}`} 
     aria-labelledby={`simple-tab-${index}`} 
     {...other} 
   > 
     {value === index ? <Box p={3}>{children}</Box> : null}
   </Typography> 

Does anyone want to give it a try?

All 26 comments

@MasterEric Thank you for taking the time to fill the issue template! We had a similar, incomplete report in the past. I would propose that we apply the same solution that we have been using with the tabs component so far:

  React.useEffect(() => {
    updateIndicatorState();
    updateScrollButtonState();
  });

In the case of the outlined text field, this translates into:

diff --git a/packages/material-ui/src/TextField/TextField.js b/packages/material-ui/src/TextField/TextField.js
index efa52da765..9820b1a5a0 100644
--- a/packages/material-ui/src/TextField/TextField.js
+++ b/packages/material-ui/src/TextField/TextField.js
@@ -94,13 +94,18 @@ const TextField = React.forwardRef(function TextField(props, ref) {

   const [labelWidth, setLabelWidth] = React.useState(0);
   const labelRef = React.useRef(null);
-  React.useEffect(() => {
+
+  const updateOutlinedLabel = React.useCallback(() => {
     if (variant === 'outlined') {
       // #StrictMode ready
       const labelNode = ReactDOM.findDOMNode(labelRef.current);
       setLabelWidth(labelNode != null ? labelNode.offsetWidth : 0);
     }
-  }, [variant, required, label]);
+  }, [variant]);
+
+  React.useEffect(() => {
+    updateOutlinedLabel();
+  });

   warning(
     !select || Boolean(children),

Hey @oliviertassinari ! I'd love to work on this issue.

@neon98 Sure, I think that it's a good one to take :).

Note, we have tried to solve the problem in #17583 but give up because of the reflow concern: https://github.com/mui-org/material-ui/pull/17583#issuecomment-535890033.

@MasterEric Thank you for taking the time to fill the issue template! We had a similar, incomplete report in the past. I would propose that we apply the same solution that we have been using with the tabs component so far:

  React.useEffect(() => {
    updateIndicatorState();
    updateScrollButtonState();
  });

In the case of the outlined text field, this translates into:

diff --git a/packages/material-ui/src/TextField/TextField.js b/packages/material-ui/src/TextField/TextField.js
index efa52da765..9820b1a5a0 100644
--- a/packages/material-ui/src/TextField/TextField.js
+++ b/packages/material-ui/src/TextField/TextField.js
@@ -94,13 +94,18 @@ const TextField = React.forwardRef(function TextField(props, ref) {

   const [labelWidth, setLabelWidth] = React.useState(0);
   const labelRef = React.useRef(null);
-  React.useEffect(() => {
+
+  const updateOutlinedLabel = React.useCallback(() => {
     if (variant === 'outlined') {
       // #StrictMode ready
       const labelNode = ReactDOM.findDOMNode(labelRef.current);
       setLabelWidth(labelNode != null ? labelNode.offsetWidth : 0);
     }
-  }, [variant, required, label]);
+  }, [variant]);
+
+  React.useEffect(() => {
+    updateOutlinedLabel();
+  });

   warning(
     !select || Boolean(children),

no work for me

any solution?

Do you guys experience the issue with the Tabs demos?

While we have a broader issue with the outlined TextField and the notch for the label, I propose that we focus on the tabs demos. I think that we can improve performance and dodge this problem with the following fix (example with one tab demo, to apply to all of them).

We can replace:
https://github.com/mui-org/material-ui/blob/5d564f9c1be5bf20b51be1a479d292bf443291ba/docs/src/pages/components/tabs/SimpleTabs.tsx#L18-L28

with

return ( 
   <Typography 
     component="div" 
     role="tabpanel" 
     hidden={value !== index} 
     id={`simple-tabpanel-${index}`} 
     aria-labelledby={`simple-tab-${index}`} 
     {...other} 
   > 
     {value === index ? <Box p={3}>{children}</Box> : null}
   </Typography> 

Does anyone want to give it a try?

Hey @oliviertassinari,
It appears that your latest solution, which deletes the contents of the tab when you switch away from it, fully fixes the rendering issue, as shown in this demo:

https://codesandbox.io/s/notch-bug-demo-fix-0ooj8

The only issue is that in the full application, this destroys components I am storing references for, and thus I will need to adapt the solution somewhat.

@MasterEric Thanks for the feedback, so I think that we should apply these changes.

Hi there, I'm not sure if I should open up a new issue, but basically I'm having the same exact issue, with the difference that I'm not using SwipeableViews, but rather a custom made hiding navigation system that I'm leveraging from a theme I bought. Should I create a new issue with my specific environment? or should I simply put more information here? Thanks.

@MomoRazor Same issue and workarounds.

We have found a solid solution in #17680. It should do it.

I am having a similar issue where I have a multi-tab form with various TextField components on it. The label gap is missing on any tab that is not the first visible tab.

any updates?

I was about to create the same exact issue.
The problem is element.offsetWidth returns 0 when the element is in an invisible tree of elements.
The only reliable solution i found is that we should take the innerText of the label, mount it in a visible span (may be at the end of body) take it offsetWidth and then remove it, the problem of this approach is that we should be aware of the used font styles... ).

I did a useInvisibleLabelWidth hook, and it's working in my use cases and working great. Let me know if interested. (I will create a small npm package for it so we can start).

I did a useInvisibleLabelWidth hook, and it's working in my use cases and working great. Let me know if interested. (I will create a small npm package for it so we can start).

Amazing 馃槃 Link please when you have it.

Here you go.
This is a working example on code sandbox.
I created a small package react-invisible-element. for now it only exports useInvisibleRefWidth whish returns the label width we need for our outlined variants.

  const inputLabel = React.useRef();
  const labelWidth = useInvisibleRefWidth(inputLabel);
  return (
     <div className="App">
       <div style={{ display: "none" }}>
         <TextField
           variant="outlined"
           value={value}
           label="Input label with issue OK"
           onChange={onChange}
           InputProps={{
             labelWidth
           }}
           InputLabelProps={{
             ref: inputLabel
           }}
         />
       </div>
     </div>
   );

The hook is just a poc for now! I believe it contains a lot of flaws and can be useful.

@incepter Once it's stable, we could potentially use your approach to fix the issue natively.

@mbrookes I think we can start with this.
I rewrote the react-invisible-elements into react-offsets which returns both the offsetWidth and offsetHeight.

The usage now is:

import useNodeOffsets from 'react-offsets';

const myPossibleHiddenElementRef = React.useRef();
const myElementOffsets = useNodeOffsets(myPossibleHiddenElementRef);
const { offsetWidth, offsetHeight } = myElementOffsets;
<Component ref={myPossibleHiddenElementRef} />

So if we rewrite Textfield to another one that may exist in an invisible tree:

function NewTextField(props) {
  let inputLabelRef = React.useRef();
  if (props?.InputLabelProps?.ref) {
    inputLabelRef = props.InputLabelProps.ref;
  }
  const offsets = useNodeOffsets(inputLabelRef);
  return (
    <TextField
      InputProps={{
        ...props.InputProps,
        labelWidth: offsets.offsetWidth,
      }}
      InputLabelProps={{
        ...props.InputLabelProps,
        ref: inputLabelRef,
      }}
      {...props}
    />
  );
}

The same applies for the Select component.

I used this code with a CPU throttle of 6 time slower than my laptop and I did not see the element created into dom (even with a style no one will be missing (color, big font size and max zIndex))

Here is the poc sandbox

UPDATE: I will add deps array to the hook to trigger the effect at needs. ie: useNodeOffsets(ref, [value, label....])

Please note that I am started using this package in my project.

I tried to change in the TextField component, the changes would look like this.

All Mui tests are passing, but I am not sure if this approach could be used natively.
Is there any contribution guides ? How can I test the new component edits locally ?

image

Is there any contribution guides ? How can I test the new component edits locally ?

https://github.com/mui-org/material-ui/blob/master/CONTRIBUTING.md#testing-the-documentation-site

I just render this item with height: px in the first tab. Then it loads label width correctly.
Same problem happens in '@material-ui/core/MobileStepper' component

@oliviertassinari i see there already a pr for this #17680, but it's been left out in the last few releases, is this something that the team is planning on fixing soon ?(i can help on pr if nedeed)

@danielgek Thank you for proposing your help. @eps1lon leads this effort. Given that the text field is, with the button, the most visited component of the documentation, I think that we should increase the priority of this resolution.

Was this page helpful?
0 / 5 - 0 ratings