Search code examples
javascriptcssreact-reduxz-index

React/Redux/CSS - Modal placement


i have a Card and a Modal component like so

class Card extends Component {
  constructor() {
    super();
    this.state = { showModal: false }
  }

  render() {
    const { showModal } = this.state;
    return (
      {/* Some code */}
      {showModal && <Modal />}
    )
  }
}

So, I don't want to render my Modal until it is required. Logic.

The Modal's job, in this context, is to display the full content of the Card component (The Card has a fixed height, so I cannot displaying every bit of information in there).


Now, at a higher level I have the following:

class App extends Component {
  /* Some Code */
  render() {
    return (
      <div>
        <Content /> {/* Our content */}
        <Overlay /> {/* Our all-purpose-overlay (we need it for the Modal) */}
      </div>
    )
  }
}

The problem is that my Card component has the style position: relative and the Modal component has the a z-index and the style position: absolute.

I did some research and I read that nested elements with non-static position will not work the same way regarding the z-index. The z-index will take effect only within the non-static element.

So I cannot make my Modal appear above the the Overlay component. Furthermore, The Card component has a overflow:hidden property. And so, the Modal gets cropped.

Here is a simple demonstration.

function toggleModal() {
  var app = document.getElementById("app");
  app.classList.toggle("-modal");
}
:root {
  background: #ddd;
}

#app,
#overlay {
  height:100vh;
  width:100%;
}

#app {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Overlay */

#overlay {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  
  visibility: hidden;
  background: rgba(0,0,0,0.3);
}

.-modal #overlay {
  visibility: visible !important;
}

/* Card */

#card {
  position: relative;
  width: 250px;
  padding: 1em;
  border-radius: 5px;
  background: white;
  overflow: hidden; /* Problem */
}

.content {
  padding: 4em 0;
  text-align: center;
}

.btn {
  padding: 1em 2em;
  border:none;
  color:white;
  float: right;
  cursor: pointer;
  transition: 0.2s ease;
  background: #00abff;
}

.btn:hover {
  background: #0c9de4;
}

/* Modal */
#modal {
  width: 300px;
  
  position: absolute;
  top:0;
  left: 50px; /* Modal Gets clipped */
  
  visibility: hidden;
  background-color:red;
}

.-modal #modal {
  visibility: visible;
}
<div id="app">
  <div id="card">
    <div class="content">content</div>
    <button class="btn" onclick="toggleModal()">Display Modal</button>
    <div id="modal">
      <div class="content">
        <div>Modal</div>
        <div>With same content</div>
        <div>(under the Overlay and also cropped)</div>
      </div>
    </div>
  </div>
  <div id="overlay" onclick="toggleModal()"></div>
</div>


Question : How should I organize my components so that I do not get this problem?

I thought of having only one Modal at top level. Then I simply display it. But I need to render the children of the Card into the Modal.

How should I proceed?


Solution

  • Since you are using React, what I would actually recommend is to use a Portal to render your modal. One thing Portals solve in React are precisely this issue. If you are using a more recent version of React (16+) then you already have access to portals.

    If you are using an older version of React, that is okay. You can use an implementation like this (react-portal) instead.

    Now you will have your components/markup rendered in a totally separate React tree outside of the context of your containing <div> that has position: relative on it and you can absolute or fixed position it wherever you need to.