react Portal - how to close modal when clicking outside of modal and how to assign ref to modal inside parent component rendering modal? - reactjs

I am using react portal and want to make it possible that modal closes when user clicks outside of modal. I am creating a ref inside my parent,but cannot really assign it to the modal itself as react portals arent actual DOM nodes (as I understood). I can also not wrap around this.props.children inside a div and assign it a ref inside my Modal component also, because then I cannot access and use it inside my Parent component.
What can I do? Thanks!
my modal:
import React from 'react';
import { createPortal } from 'react-dom';
const modalRoot = document.getElementById( 'modal' );
class Modal extends React.Component {
constructor( props ) {
super( props );
this.element = document.createElement( 'div' );
}
componentDidMount() {
modalRoot.appendChild( this.element );
}
componentWillUnmount() {
modalRoot.removeChild( this.element );
}
render() {
return createPortal(this.props.children, document.querySelector('#modal'));
}
}
the parent component rendering modal:
class Parent extends React.Component {
constructor (props) {
super(props);
this.state = {
showModal: false
};
this.modal=React.createRef();
}
showModal = () => {
this.setState({
showModal: !this.state.showModal
})
};
closeModal = (e) => {
if (this.modal.current.contains(e.target)) {
return;
} else {
this.showModal()
}
}
componentDidMount () {
document.addEventListener('click', this.closeModal, false)
}
componentWillUnmount() {
document.removeEventListener('click', this.closeModal, false)
}
render () {
const {src, height, width} = this.props;
return (
<React.Fragment>
<img
className="image"
src={src}
onClick={this.showModal}
/>
{
this.state.showModal ? (
<Modal>
<div className="my-modal">
<h1 >Heading</h1>
<p>Lorem ipsum </p>
<button
className="modal-close"
onClick={this.showModal}
>X
</button>
</div>
</Modal>
) : null
}
</React.Fragment>
)
}
};

Since the click listener will be applied to all modal instances, I'd move the listener and the ref to a reuseable modal component -- now it'll control clicks once opened. The only thing it really needs is a passed down toggle modal function prop from the parent.
Working example:
components/Modal/index.js
import React, { Fragment, PureComponent } from "react";
import { createPortal } from "react-dom";
import PropTypes from "prop-types";
class Modal extends PureComponent {
componentDidMount() {
document.addEventListener("click", this.closeModal, false);
}
componentWillUnmount() {
document.removeEventListener("click", this.closeModal, false);
}
closeModal = ({ target }) => {
if (this.modal && !this.modal.contains(target)) {
this.props.toggleModal();
}
};
render = () =>
createPortal(
<Fragment>
<div className="overlay" />
<div className="window-container">
<div className="modal-container">
<div ref={node => (this.modal = node)} className="modal">
{this.props.children}
</div>
</div>
</div>
</Fragment>,
document.body
);
}
Modal.propTypes = {
children: PropTypes.node.isRequired,
toggleModal: PropTypes.func.isRequired
};
export default Modal;
components/Parent/index.js
import React, { Component } from "react";
import Modal from "../Modal";
class Parent extends Component {
state = {
showModal: false
};
toggleModal = () => {
this.setState(prevState => ({
showModal: !prevState.showModal
}));
};
render = () => (
<div className={`${this.state.showModal ? "blur" : undefined} app`}>
<img
src="https://i.imgur.com/BGwgr3A.jpg"
className="image"
alt="example.png"
onClick={this.toggleModal}
/>
{this.state.showModal && (
<Modal toggleModal={this.toggleModal}>
<h1 className="title">Hello!</h1>
<p className="subtitle">There are two ways to close this modal</p>
<ul>
<li>Click outside of this modal in the grey overlay area.</li>
<li>Click the close button below.</li>
</ul>
<button
className="uk-button uk-button-danger uk-button-small"
onClick={this.toggleModal}
>
Close
</button>
</Modal>
)}
</div>
);
}
export default Parent;
styles.css
.app {
text-align: center;
margin-top: 20px;
}
.blur > img {
-webkit-filter: blur(10px);
-moz-filter: blur(10px);
-ms-filter: blur(10px);
-o-filter: blur(10px);
filter: blur(10px);
}
.image {
display: block;
margin: 0 auto;
cursor: pointer;
width: 600px;
}
.modal {
max-width: 600px;
max-height: calc(100% - 96px);
padding: 20px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
position: relative;
overflow-y: auto;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-shadow: 0px 11px 15px -7px rgba(0, 0, 0, 0.2),
0px 24px 38px 3px rgba(0, 0, 0, 0.14), 0px 9px 46px 8px rgba(0, 0, 0, 0.12);
box-shadow: 0px 11px 15px -7px rgba(0, 0, 0, 0.2),
0px 24px 38px 3px rgba(0, 0, 0, 0.14), 0px 9px 46px 8px rgba(0, 0, 0, 0.12);
border-radius: 4px;
background-color: #fff;
text-align: left;
}
.modal-container {
opacity: 1;
-webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
-o-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
height: 100%;
outline: none;
}
.overlay {
opacity: 1;
-webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
-o-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
position: fixed;
-ms-touch-action: none;
touch-action: none;
background-color: rgba(0, 0, 0, 0.5);
-webkit-tap-highlight-color: transparent;
}
.subtitle {
margin: 0;
text-align: center;
font-weight: bold;
}
.title {
text-align: center;
}
.window-container {
text-align: center;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
outline: 0;
-webkit-animation: fadeIn 0.2s 0s ease-in-out forwards;
animation: fadeIn 0.2s 0s ease-in-out forwards;
z-index: 100;
}

Related

Mapped buttons that have opacity of 0 are showing, and are overlapping when resizing the window

I've successfully mapped over text and have styled it with transition effects when going from one slide to the next as you can see here:
Following a similar concept with buttons isn't working. There should only be one button active per slide like you see here:
I want the buttons to have the same effect as the text, but I'm getting behaviors like you see here:
As you can see, there is no transition effect on the button when clicking to the second slide, and it also appears in a lower spot.
And lastly, when resizing the window, buttons are overlapping like you see here:
Don't know what to try next.
Here's the ImageSlider component:
import { useState } from "react";
import { SliderData } from "../data";
import { categories } from "../data";
import ShopNowButtonActive from "./ShopNowButtonActive";
import { IoIosArrowBack } from "react-icons/io";
import { IoIosArrowForward } from "react-icons/io";
import "./ImageSlider.css";
import ShopNowButton from "./ShopNowButton";
const ImageSlider = ({ slides }) => {
const [current, setCurrent] = useState(0);
const length = slides.length;
const nextSlide = () => {
setCurrent(current === length - 1 ? 0 : current + 1);
};
const prevSlide = () => {
setCurrent(current === 0 ? length - 1 : current - 1);
};
return (
<div className="slider">
<IoIosArrowBack className="left-arrow" onClick={prevSlide} />
{SliderData.map((slide, index) => (
<div key={slide.id}>
<img
src={slide.img}
alt=""
className={index === current ? "slide active" : "slide"}
/>
<div className="info-container">
<div className={index === current ? "title active" : "title"}>
{slide.title}
</div>
<div className={index === current ? "desc active" : "desc"}>
{slide.desc}
</div>
{categories.map((item, index) =>
index === current ? (
<ShopNowButtonActive item={item} />
) : (
<ShopNowButton item={item} />
)
)}
</div>
</div>
))}
<IoIosArrowForward className="right-arrow" onClick={nextSlide} />
</div>
);
};
export default ImageSlider;
The css file:
.slider {
height: 90vh;
margin-bottom: 0.5rem;
}
.left-arrow {
position: absolute;
top: 45%;
left: 32px;
font-size: 2rem;
cursor: pointer;
opacity: 0.5;
z-index: 1;
}
.slide.active {
opacity: 1;
width: 100%;
height: 88%;
object-fit: cover;
object-position: center;
-webkit-clip-path: polygon(100% 0, 100% 80%, 50% 100%, 0 80%, 0% 0%);
clip-path: polygon(100% 0, 100% 80%, 50% 100%, 0 80%, 0% 0%);
}
.slide {
opacity: 0;
transition: 500ms opacity ease-in-out;
width: 100%;
height: 88%;
object-fit: cover;
object-position: center;
position: absolute;
-webkit-clip-path: polygon(100% 0, 100% 80%, 50% 100%, 0 80%, 0% 0%);
clip-path: polygon(100% 0, 100% 80%, 50% 100%, 0 80%, 0% 0%);
}
.info-container {
display: flex;
flex-direction: column;
width: 40%;
height: 100%;
position: absolute;
top: 45%;
right: 30px;
}
.title.active {
opacity: 1;
transition-delay: 700ms;
font-size: 4rem;
}
.title {
opacity: 0;
transition: 200ms opacity ease-in-out;
font-size: 4rem;
}
.desc.active {
opacity: 1;
padding-top: 1.5em;
transition-delay: 700ms;
font-size: 1.25rem;
font-weight: 500;
letter-spacing: 3px;
}
.desc {
opacity: 0;
padding-top: 1.5em;
transition: 200ms opacity ease-in-out;
font-size: 1.25rem;
font-weight: 500;
letter-spacing: 3px;
}
.right-arrow {
position: absolute;
top: 45%;
right: 32px;
font-size: 2rem;
cursor: pointer;
opacity: 0.5;
z-index: 1;
}
The ShopNowButtonActive component:
import React from "react";
import styled from "styled-components/macro";
import { Link } from "react-router-dom";
const ButtonActive = styled.button`
opacity: 1;
padding: 0.5rem;
margin-top: 2.5rem;
width: 8rem;
font-size: 20px;
background-color: transparent;
cursor: pointer;
transition-delay: 700ms;
`;
const ShopNowButtonActive = ({ item }) => {
return (
<Link to={`/products/${item.cat}`}>
<ButtonActive>SHOP NOW</ButtonActive>
</Link>
);
};
export default ShopNowButtonActive;
And finally, the ShopNowButton component:
import React from "react";
import styled from "styled-components/macro";
import { Link } from "react-router-dom";
const Button = styled.button`
opacity: 0;
/* display: none; */
padding: 0.5rem;
margin-top: 2.5rem;
width: 8rem;
font-size: 20px;
background-color: transparent;
cursor: pointer;
transition: 200ms opacity ease-in-out;
`;
const ShopNowButton = ({ item }) => {
return (
<Link to={`/products/${item.cat}`}>
<Button>SHOP NOW</Button>
</Link>
);
};
export default ShopNowButton;
(Sorry for the use of both an external css file and styled components.)
Any suggestions?
I have recreated the above scenario using some static data. I have modified some of the css. It is working as expected. I also observed that the only major difference between ShopNowButton and ShopNowButtonActive was opacity property which hides the element. The reason you are observing 2 buttons because they were all there in the dom actually.(They were not hiding properly due to which we are observing more shop now buttons and every time we click on next icon the corresponding button is being displayed. Basically all the buttons are there on the page itself.)
Please find the sandbox url below.
https://codesandbox.io/s/stackoverflow-6u05e7?file=/src/ImageSlider/ImageSlider.js

ReactDOM.createPortal modal is mounted on DOM but nothing is displayed on the screen

this is a typescript-next.js project. I have this Modal component:
interface ModalProps {
onCancelModal: () => void;
onAcceptModal: () => void;
acceptEnabled: boolean;
isLoading?: boolean;
title: string;
}
const Modal: React.FC<ModalProps> = (props) => {
let containerRef = useRef<HTMLDivElement | null>(null);
console.log("container", containerRef);
useEffect(() => {
const rootContainer = document.createElement("div");
const parentElem = document.querySelector("#__next");
parentElem?.insertAdjacentElement("afterend", rootContainer);
if (!containerRef.current) {
containerRef.current = rootContainer;
}
return () => rootContainer.remove();
}, []);
return containerRef.current
? ReactDOM.createPortal(
<div className="modal">
<header className="modal__header">
<h1>{props.title}</h1>
</header>
<div className="modal__content">{props.children}</div>
<div className="modal__actions">
<Button design="danger" mode="flat" onClick={props.onCancelModal}>
Cancel
</Button>
<Button
mode="raised"
onClick={props.onAcceptModal}
disabled={!props.acceptEnabled}
loading={props.isLoading}
>
Accept
</Button>
</div>
</div>,
containerRef.current
)
: null;
};
export default Modal;
I pass a custom error to ErrorHandler component:
const ErrorHandler: React.FC<ErrorHandlerProps> = (props) => (
<Fragment>
{props.error && <Backdrop onClick={props.onHandle} />}
{props.error && (
<Modal
title="An Error Occurred"
onCancelModal={props.onHandle}
onAcceptModal={props.onHandle}
acceptEnabled
>
<p>{props.error}</p>
</Modal>
)}
</Fragment>
);
However, Modal component is successfully mounted on the DOM but nothing displays on the screen.
EDIT
I have backdrop and modal components.
// css for backdrop
.backdrop {
width: 100%;
height: 100vh;
background: rgba(0, 0, 0, 0.75);
z-index: 100;
position: fixed;
left: 0;
top: 0;
transition: opacity 0.3s ease-out;
opacity: 1;
}
// css for Modal
.modal {
position: fixed;
width: 90%;
left: 5%;
top: 20vh;
background: white;
border-radius: 5px;
z-index: 200;// I changed this to 999999 but didnot solve the issue
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
}
.modal__header {
border-bottom: 2px solid #3b0062;
}
.modal__header h1 {
font-size: 1.5rem;
color: #3b0062;
margin: 1rem;
}
.modal__content {
padding: 1rem;
}
.modal__actions {
padding: 1rem;
text-align: right;
}
.modal__actions button {
margin: 0 0.5rem;
}
#media (min-width: 768px) {
.modal {
width: 40rem;
left: calc((100% - 40rem) / 2);
}
}
I found the answer after i refresh my memory. I realized that there is another .modal className on elements-styles tab. It points me to the /node_modules/bootstrap/scss/_modal.scss file which also has modal className and it was overriding my custom className.
.modal {
position: fixed;
top: 0;
left: 0;
z-index: $zindex-modal;
display: none;
width: 100%;
height: 100%;
overflow: hidden;
// Prevent Chrome on Windows from adding a focus outline. For details, see
// https://github.com/twbs/bootstrap/pull/10951.
outline: 0;
// We deliberately don't use `-webkit-overflow-scrolling: touch;` due to a
// gnarly iOS Safari bug: https://bugs.webkit.org/show_bug.cgi?id=158342
// See also https://github.com/twbs/bootstrap/issues/17695
}

How to change background color with toggle button in React?

In Weather project, I have toggle button for Day/Night mode. When toggled it should change background color but it fills the entire page above the components which isn't desirable. Is there any appropriate solution?
Below is the reference
DayNightMode.js
const DayNightMode = () => {
return (
<div className="switch-box">
<div className="switch">
<label for="toggle">
<input id="toggle" className="toggle-switch" type="checkbox" />
<div className="sun-moon">
<div className="dots">
</div>
</div>
<div className="background">
<div className="stars1"></div>
<div className="stars2"></div>
</div>
<div className="fill"></div>
</label>
</div>
</div>
);
};
export default DayNightMode;
DayNightMode.css
* {
box-sizing: border-box;
}
.switch-box {
margin-left: 30%;
/* margin-top: -5%; */
}
.container {
/* height: calc(100% - 2.5rem); */
/* background: #f4f4f4; */
/* display: flex; */
/* justify-content: center; */
/* align-items: center;*/
}
.switch {
position: relative;
/* overflow: hidden; */
width: 8.6rem;
/* height: 2rem; */
}
.switch input {
position: absolute;
top: 0;
left: 0;
z-index: 2;
opacity: 0;
}
.switch label {
cursor: pointer;
}
.background {
z-index: 1;
position: absolute;
width: 6.5rem;
height: 1.8rem;
border-radius: 2.5rem;
border: 0.25rem solid #202020;
background: linear-gradient(to right, #484848 0%, #202020 100%);
transition: all 0.3s;
margin-top: -22px;
opacity: 0.8;
}
.fill {
/* background: #484848;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0; */
}
.switch input:checked ~ .fill {
background: #e9f8fd;
}
/* .fill {
position: fixed;
top: 0;
right: 0;
bottom: 2rem;
left: 0;
background: #484848;
transition: 0.75s all ease;
} */
/* .switch input:checked ~ .fill {
background: #E9F8FD;
} */
/* Stars */
.stars1,
.stars2 {
position: absolute;
height: 0.3rem;
width: 0.3rem;
background: #ffffff;
border-radius: 50%;
transition: 0.3s all ease;
}
.stars1 {
top: 2px;
right: 20px;
}
.stars2 {
top: 20px;
right: 35px;
}
.stars1:after,
.stars1:before,
.stars2:after,
.stars2:before {
position: absolute;
content: "";
display: block;
height: 0.25rem;
width: 0.25rem;
background: #ffffff;
border-radius: 50%;
transition: 0.2s all ease;
}
.stars1:after {
top: 2px;
right: 20px;
}
.stars1:before {
top: 12px;
right: -12px;
}
.stars2:after {
top: -20px;
right: -20px;
}
.stars2:before {
top: -24px;
right: -30px;
}
.sun-moon {
z-index: 2;
position: absolute;
left: 0;
display: inline-block;
height: 1.5rem;
width: 1.5rem;
margin: 0.12rem;
background: #fffdf2;
border-radius: 50%;
transition: all 0.5s ease;
margin-top: -20.5px;
/* Default to Moon */
border: 0.2rem solid #dee2c6;
}
.sun-moon .dots {
position: absolute;
top: 0.5px;
left: 42px;
height: 0.2rem;
width: 0.2rem;
background: #efeedb;
border: 0.25rem solid #dee2c6;
border-radius: 50%;
transition: 0.4s all ease;
}
.sun-moon .dots:after,
.sun-moon .dots:before {
position: absolute;
content: "";
display: block;
height: 0.25rem;
width: 0.25rem;
background: #efeedb;
border: 0.25rem solid #dee2c6;
border-radius: 50%;
transition: 0.4s all ease;
}
.sun-moon .dots:after {
top: -8px;
left: -26px;
}
.sun-moon .dots:before {
top: 10px;
left: -10px;
}
/* Transition to Sun */
.switch input:checked ~ .sun-moon {
left: calc(100% - 4rem);
background: #f5ec59;
border-color: #e7c65c;
transform: rotate(-25deg);
}
.switch input:checked ~ .sun-moon .dots,
.switch input:checked ~ .sun-moon .dots:after,
.switch input:checked ~ .sun-moon .dots:before {
background: #ffffff;
border-color: #ffffff;
}
.switch input:checked ~ .sun-moon .dots {
height: 1.2rem;
width: 1.2rem;
top: -10px;
left: -20px;
transform: rotate(25deg);
}
.switch input:checked ~ .sun-moon .dots:after {
height: 0.65rem;
width: 0.65rem;
top: 2px;
left: -12px;
}
.switch input:checked ~ .sun-moon .dots:before {
height: 0.4rem;
width: 0.4rem;
top: 6px;
left: 14px;
}
.switch input:checked ~ .background .stars1,
.switch input:checked ~ .background .stars2 {
opacity: 0;
transform: translateY(2rem);
}
.switch input:checked ~ .background {
border: 0.25rem solid #78c1d5;
background: linear-gradient(to right, #78c1d5 0%, #bbe7f5 100%);
}
Form.js (DayNight Toggle button defined here)
import React from 'react'
import { ArrowTooltip } from './ArrowTooltip'
import gps from '../images/gps.png'
import 'react-toastify/dist/ReactToastify.css';
import Autosuggest from 'react-autosuggest';
import { highlight } from './Helper'
import cities from 'cities.json';
import Button from 'react-bootstrap/Button'
import './Form.css';
import { MDBContainer,
MDBRow,
MDBCol,
MDBCard,
MDBCardBody,
MDBCardHeader,
MDBBtn} from 'mdbreact'
import DayNightMode from './DayNightMode';
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
suggestions: [],
newsValue: '',
weatherValue: ''
};
}
onChange = (event, { newValue}) => {
this.setState({
value: newValue,
});
};
render(){
return (
<div>
<MDBContainer style={{height: '480px', marginTop: '25px'}}>
<MDBRow>
<MDBCol>
<MDBCard style={sectionStyle}></MDBCard>
<MDBCard style={{zIndex:'1', background: 'none'}}>
<MDBCardBody>
{/* Here Toggle button is defined */}
<DayNightMode/>
<form>
<div width="100%">
<ArrowTooltip title="Track Location" placement="top">
<span style={{width:'10%', display:'inline-block', cursor:'pointer'}}
onClick={this.props.fetchWeather}>
<img src={gps} width="25px" height="25px" />
</span>
</ArrowTooltip>
</div>
<br/>
<div className="text-center mt-4">
<Button variant="info"
className="mb-3 btn-block"
type="submit"
value={inputProps.value}
onClick={e => this.onClick(e)}
style={{background: '#e0f7fa', opacity:'0.6',
borderRadius: '10px',fontFamily: 'Josefin Sans',
boxShadow: '0 8px 6px -6px black'}}
>Search Weather</Button>
</div>
</form>
</MDBCardBody>
</MDBCard>
</MDBCol>
</MDBRow>
</MDBContainer>
</div>
)
}
}
export default Form;
App.js (Parent File)
import React from "react";
import "./App.css";
import Form from "./components/Form";
import { ToastContainer, toast, Bounce } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import WeatherAndNews from "./components/WeatherAndNews";
import moment from "moment-timezone";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
newsValue: "",
weatherValue: ""
};
}
handleNews = async (data) => {
this.setState({
newsValue: data
});
};
handleWeather = async (data) => {
this.setState({
weatherValue: data
});
};
render() {
return (
<React.Fragment>
<div className="main-container">
<div className="form-container">
<Form
newsValue={this.state.newsValue}
weatherValue={this.state.weatherValue}
/>
<ToastContainer transition={Bounce} className="toast-background" />
</div>
<div className="body-container">
<WeatherAndNews />
</div>
</div>
</React.Fragment>
);
}
}
export default App;
My intention is that background color of the page should be changed on toggle
Following is the Codesandbox link https://codesandbox.io/s/2huux
Steps you need to do to achieve this:
Create a variable named 'isDayMode' in App.js component
Create a function with the name 'handleDayNightToggle' in App.js which can update that value to true or false on the basis of the checkbox selected
Pass the reference of the function as props to the Form component
Again pass the reference of function 'handleDayNightToggle' from Form to DayNightMode component
There create an onClick function for the checkbox and make it call the 'handleDayNightToggle' passed as props. So any time the checkbox is clicked this function calls the 'handleDayNightToggle' of the App component.
Finally use the flag 'isDayMode' in App component to understand whether the day is selected or night and accordingly change the classes to update background color.
Updated your project with the above steps, take a look into it - https://codesandbox.io/s/async-cache-r1poy

The clientWidth property of ref button return undefined

Using the targetButton refs to get the Button dom, and I want to get clientWidth property when click this Button.
import React from "react";
import styled from "styled-components";
const Container = styled.div`
width: 600px;
height: 600px;
background-color: black;
`;
const Button = styled.button`
font-size: 24px;
padding: 1em 2em;
margin: 3px;
border: 0;
outline: 0;
color: white;
background-color: #2196f3;
border-radius: 0.15em;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
-webkit-appearance: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-tap-highlight-color: transparent;
`;
const Ripple = styled.div.attrs({
size: props => props.size
})`
width: ${props => props.size};
height: ${props => props.size};
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.7);
position: absolute;
`;
class RippleButton extends React.Component {
state = {
rippleNum: 0
};
addRipple() {
const { clientWidth, clientHeight } = this.targetButton;
console.log(clientWidth);
this.setState({
rippleNum: this.state.rippleNum + 1
});
}
render() {
const children = [];
for (let i = 0; i < this.state.rippleNum; i += 1)
children.push(
<Ripple
size={Math.max(
this.targetButton.clientWidth,
this.targetButton.clientHeight
)}
key={i}
/>
);
return (
<Container>
<Button
ref={elem => {
this.targetButton = elem;
}}
onClick={this.addRipple.bind(this)}
>
Button{children}
</Button>
</Container>
);
}
}
export default RippleButton;
The console reports undefined error message, after having search many ways to tweak it.
The problem in this code is when using styled-components, you should use innerRef instead of ref.

React-modal hides behind elements

I am trying to make use of react-modal for the first time. When I click on the sign-in button, the react-modal component is invoke but seems to be hiding behind the cover page which is a video landing page.
The React devtool displays the appropriate states before the sign-in button is clicked
before the sign-in button is clicked
When the sign-in button is now clicked, the react devtool now displays that the ModalPortal component is rendered showing the appropriate states
when the sign-in button is clicked
SignInModal.scss
.ReactModalPortal>div {
opacity: 0;
}
.ReactModalPortal .ReactModal__Overlay {
align-items: center;
display: flex;
justify-content: center;
transition: opacity 200ms ease-in-out;
}
.ReactModalPortal .ReactModal__Overlay--after-open {
opacity: 1;
}
.ReactModalPortal .ReactModal__Overlay--before-close {
opacity: 0;
}
.modal {
position: relative;
background: #464b5e;
color: white;
max-width: 90rem;
outline: none;
padding: 3.2rem;
text-align: center;
}
.modal__title {
margin: 0 0 1.6rem 0;
}
.modal__body {
font-size: 2rem;
font-weight: 300;
margin: 0 0 3.2rem 0;
word-break: break-all;
}
CoverPage.js Component
import Header from './Header';
import HeaderVideo from './HeaderVideo';
import SignInModal from './SignInModal';
import React, { Component } from 'react';
class CoverPage extends Component {
state = {
modalIsOpen: false
};
onOpenModal = () => {
this.setState(() => ({
modalIsOpen: true
}));
};
onCloseModal = () => {
this.setState(() => ({
modalIsOpen: false
}));
};
render() {
return (
<div>
<Header />
<HeaderVideo onOpenModal={this.onOpenModal} />
<SignInModal
modalIsOpen={this.state.modalIsOpen}
onOpenModal={this.onOpenModal}
onCloseModal={this.onCloseModal}
/>
</div>
);
}
}
export default CoverPage;
HeaderVideo.js Component
import React from 'react';
import Signup from './Signup';
import CoverInfo from './CoverInfo';
const HeaderVideo = props => {
return (
<div className="video-container">
<video preload="true" autoPlay loop volume="0" postoer="/images/1.jpg">
<source src="images/vine.mp4" type="video/mp4" />
<source src="images/vine1.webm" type="video/webm" />
</video>
<div className="video-content">
<div className="container content">
<div className="row">
<div className="col-md-9">
<CoverInfo onOpenModal={props.onOpenModal} />
</div>
<div className="col-md-3">
<Signup />
</div>
</div>
</div>
</div>
</div>
);
};
export default HeaderVideo;
CoverInfo.js Component
import React from 'react';
const CoverInfo = props => {
return (
<div className="info">
<div>
<h1>Welcome to EventCity!</h1>
</div>
<div>
<p>
At EventCity! we pride ourselves on the unrivalled personal {`event`} services,we provide
to our clientele. We guide you from the stressful decision making {`process`},ensuring you
are comfortable,whether it is a wedding, corporate {`function `}or even a kiddies party,we
create a buzz around you, taking you to the next level.
</p>
</div>
<div>
<h3>Innovation, {`Performance`} and Delivery</h3>
</div>
<button type="button" className="btn btn-success btn-lg" onClick={props.onOpenModal}>
Sign In here
</button>
</div>
);
};
export default CoverInfo;
video-cover.scss
video {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
min-width: 100%;
min-height: 100%;
width: auto;
height: auto;
z-index: 1;
}
.video-content {
z-index: 2;
position: absolute;
background: rgba(0, 0, 0, 0.6);
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.content {
padding-top: 120px;
}
You need to set the z-index property on the Modal's overlay, which normally has a z-index of 0. The CSS class is .ReactModal__Overlay
Here is the pure-React way of doing it:
const customStyles = {
content : {
...
},
overlay: {zIndex: 1000}
};
<Modal style={customStyles}>
...
</Modal>
.modal {
position: fixed;
z-index:9999;
top :0;
left:0;
right:0;
bottom:0;
background: #464b5e;
color: white;
outline: none;
padding: 3.2rem;
text-align: center;
}
Example of react-modal inline styles Set the styles in the react-modal inline styles. The z-index to 100 but make just like below
style={{
overlay: {
zIndex: 100,
backgroundColor: 'rgba(70, 70, 70, 0.5)',
},

Resources