React-md: Nested Dialogs don't display correctly

Created on 25 Jan 2017  路  10Comments  路  Source: mlaursen/react-md

Description

If you have a Dialog component with a DatePicker field inside, then when you open the DatePicker, it is displayed behind the Dialog.

If you look at the DOM, you'll see, that when Dialog is opened, new Span element is added as a first child of Body. When DatePicker dialog is opened, then the new Span element, which contains picker's UI is again added as a first child of Body. If I reorder these Span elements in Chrome DevTools, then DatePicker is displayed correctly.

Images/Screenshots

screenshot 2017-01-25 18 52 53

Link to a gist or code sample where the issue can be reproduced

App class:

import React, { PureComponent } from 'react';
import Dialog from 'react-md/lib/Dialogs';
import Button from 'react-md/lib/Buttons/Button';
import DatePicker from 'react-md/lib/Pickers/DatePickerContainer';

export default class App extends PureComponent {
  constructor(props) {
    super(props);
    this.state = { visible: false };
  }

  openDialog = () => {
    this.setState({ visible: true });
  };

  closeDialog = () => {
    this.setState({ visible: false });
  };

  render() {
    const { visible } = this.state;
    return (
      <div>
        <Button raised onClick={this.openDialog} label="Open Modal Dialog" />
        <Dialog
          id="speedBoost"
          visible={visible}
          title="Use Google's location service?"
          onHide={this.closeDialog}
          aria-labelledby="speedBoostDescription"
          modal
          actions={[
          {
            onClick: this.closeDialog,
            primary: true,
            label: 'OK',
          }]}
        >
          <DatePicker/>
        </Dialog>
      </div>
    );
  }
}

Version

  • React 15.4.2,
  • React-MD 1.0.1
bug

Most helpful comment

With the latest fix, you will no longer have to do any work yourself. If any component that uses the Portal component appears within the children of the Dialog, it will automatically render as a child in the dialog instead of rendering in the document.body by default.

<Dialog id="full-page" visible={fullPageVisible} onHide={this.hideFullPage}>
  <DatePicker id="birthdate" />
  <TimePicker id="appointment-time" />
  <Drawer id="nested-drawer" { ... } />
  <Snackbar { ... } />
  <Dialog { ... } />
</Dialog>

The date picker, time picker, snackbar, drawer, and dialog should now be rendered inside of the full page dialog.

All 10 comments

An inline picker could be a workaround. Also it looks like there are some sass variables in the dialogue component $md-dialog-overlay-z-index and $md-dialog-full-page-z-index that might could be changed to be below the index of the picker.

Yeah, I think that could work (and we are going to use inline picker for now), but I'd still expect that the library works correctly out of the box.

Yeah, this should be fixed in the library. I'll think about how I want to fix it. It is really a problem with using the Portal component for dialogs -- I should either update the Pickers to pass the renderNode and lastChild props correctly to the dialog, or update the z-index for .md-dialog.md-dialog--picker to be higher, or update the Portal component to optionally NOT render as a subtree (eventual feature that should be added).

I'm not very familiar with the internal structure of react-md, but renderNode and lastChild solution sounds good. What if we have a dialog in dialog (which is not something rare) and the latter one has a DatePicker? Thanks for the response!

As a side note.. Thanks for following the template I added a a few days ago! It is actually a lot more helpful than I thought it would be in figuring out bugs. Wish I had added it earlier.

For right now, the lastChild and renderNode prop additions will suffice until the Portal updates.

I don't feel like the current patch is good enough before 1.1.0, so I am going to implement an automatic way to have it work without requiring lastChild and renderNode.

With the latest fix, you will no longer have to do any work yourself. If any component that uses the Portal component appears within the children of the Dialog, it will automatically render as a child in the dialog instead of rendering in the document.body by default.

<Dialog id="full-page" visible={fullPageVisible} onHide={this.hideFullPage}>
  <DatePicker id="birthdate" />
  <TimePicker id="appointment-time" />
  <Drawer id="nested-drawer" { ... } />
  <Snackbar { ... } />
  <Dialog { ... } />
</Dialog>

The date picker, time picker, snackbar, drawer, and dialog should now be rendered inside of the full page dialog.

I'm having troubles with components that use Portal in [email protected] when using with react-redux connect method @mlaursen .

actually, for what I've tested, this issue is happening with any PureComponent that I use inside a Portal component and that contains another Portal component. It seens that it's rendering into the body the nested Portal component that is inside a PureComponent.

You can easily test this by just adding react-redux connect method to a DatePicker inside a Dialog

const ConnectedDatePicker = connect()(() => <DatePicker id="date" label="Select date"/>)

const DialogTest = () => (
  <Dialog visible modal aria-label="new-dialog" id="new-dialog">
      <ConnectedDatePicker/>
  </Dialog>
)

Or by creating a dummy PureCompoent

class PurePicker extends PureComponent {
  render () { return <DatePicker id="date" label="Select date"/> }
}
const DialogTest = () => (
  <Dialog visible modal aria-label="new-dialog" id="new-dialog">
      <PurePicker/>
  </Dialog>
)

The first fix for this was using context to set the render node, but when you use PureComponent, it doesn't pass it to the sub-portal. I think I'll have to modify it so that the portal itself will check if it is in position: fixed and set the renderNode related to that.

Was this page helpful?
0 / 5 - 0 ratings