material-components / material-components-web-react

Material Components for React (MDC React)
MIT License
2.02k stars 227 forks source link

Menu not anchoring to correct part of anchorElement first time only #913

Open ndtreviv opened 5 years ago

ndtreviv commented 5 years ago

Code


setAnchorElement = element => {
    if (this.state.anchorElement) {
      return;
    }
    this.setState({ anchorElement: ReactDOM.findDOMNode(element) });
};

render() {
...
         <TextField
            // label="Field"
            ref={this.setAnchorElement}
            trailingIcon={<MaterialIcon role="button" icon="arrow_drop_down" />}
            onTrailingIconSelect={e => {
              this.setState({ fieldListOpen: true });
            }}
          >
            <Input
              ref={"input"}
              onClick={e => {
                this.setState({ fieldListOpen: true });
              }}
              onChange={e => {
                this.setState({
                  fieldFilter: e.target.value,
                  fieldListOpen: true
                });
              }}
              value={fieldFilter || selectedField.label}
            />
          </TextField>

          <Menu
            open={fieldListOpen}
            onClose={e => {
              this.setState({ fieldListOpen: false });
              // We then have to blur the input otherwise the label stays populated:
              this.refs.input.handleBlur();
            }}
            anchorCorner={Corner.BOTTOM_LEFT}
            anchorElement={anchorElement}
          >
            <MenuList>
              {options.map((field, index) => (
                <MenuListItem
                  key={index}
                  onClick={e => {
                    this.setState({
                      selectedField: field,
                      fieldFilter: field.label
                    });
                  }}
                >
                  <MenuListItemText primaryText={field.label} />
                </MenuListItem>
              ))}
            </MenuList>
          </Menu>
...
}

First time I click the Input or the trailing icon (anchoring is incorrect):

Screenshot 2019-06-06 at 11 00 20

Subsequent clicks (anchoring is correct):

Screenshot 2019-06-06 at 11 00 27

I followed the example in the docs for this, except for this bit:

    this.setState({ anchorElement: ReactDOM.findDOMNode(element) });

which was incorrect in the docs, as they were setting anchorElement to just be element.

Debug shows that the anchorElement doesn't change (when I rollover both it highlights the same input):

Screenshot 2019-06-06 at 11 04 23
ndtreviv commented 5 years ago

I've also tried:

            anchorElement={this.refs.input && this.refs.input.inputElement}

Same behaviour. I think it's because the anchor element is null first time around.

ndtreviv commented 5 years ago

Looks like the the first time around, the dom element is there, but the coordinates are wrong:

Screenshot 2019-06-06 at 11 16 17
ndtreviv commented 5 years ago

Ok, now I'm thinking it's because the whole lot is in a Dialog, which is still "opening" when the DOM reference is obtained

ndtreviv commented 5 years ago

That wasn't it. Now I'm setting the anchor on componentDidMount.

I've even tried using coordinates instead:

    if (!this.state.listCoordinates) {
      const rect = ReactDOM.findDOMNode(this.inputRef.current).getBoundingClientRect();
      const listCoordinates = {
        x: rect.x,
        y: rect.y + rect.height
      }
      this.setState({listCoordinates: listCoordinates});
    }

The coordinates come out as null on first render, obviously, and then correct on second render, but even after second render the menu still doesn't render in the right place the first time around.

ndtreviv commented 5 years ago

First time it opens there is no top in the inline style:

transform-origin: center bottom; left: 440px; bottom: 499px; max-height: 388px;

Second time it opens, top is correct. So it's clearly getting left correctly, but not top?

If I take off the anchorCorner property, it renders aligned top top-left of the TextField element first time. This would be ok, except that it's overlaying the TextField.

If I set anchorCorner to TOP_RIGHT then it's fine as well. Just BOTTOM_ options are screwed in this case.

ndtreviv commented 5 years ago

If I leave off anchorCorner (so that it would anchor to the TOP_LEFT Corner of the TextField), and then set anchorMargin as:

    anchorMargin={{top: 56, bottom: 0, left: 0, right: 0}}

that also doesn't work. It just renders like the first picture in the original post.

ndtreviv commented 5 years ago

At the moment, the hacky workaround is to set the damn style myself:

            style={{left: anchorElement.getBoundingClientRect().x, top: anchorElement.getBoundingClientRect().y + anchorElement.getBoundingClientRect().height}}

I'm off for a drink 🤪

moog16 commented 5 years ago

@ndtreviv https://github.com/material-components/material-components-web-react/issues/913#issuecomment-499436789 this might be because we have a default anchor rect element, but once the anchor element is created, it should take the default's place. Do you have a test project that you're testing this all in that you can share?