In my react app I have a hidden banner that I want to show, when the length of the array reaches to 5. But it looks like that I am trying to get an element before it is rendered. I get the error about getting a style of undefined element.
This function must change css of the banner element and make it visible.
showBanner() {
let banner = document.getElementsByClassName('overlay')[0]
banner.style.cssText = "visibility: visible;opacity: 1;"
}
I want to render my popup component only if the condition is met.
render() {
if (this.props.awarded) {
if (this.props.awarded.length === 5) {
this.showBanner()
return (
<>
<h1 id="awardLabel">5 movies</h1>
<div id="movieList">
{this.props.awarded.map((movie) => {
return (
<div className="awardHolder" key={movie.imdbID}>
<div className="awardImgHolder" >
<img src={movie.Poster} alt={movie.Title}></img>
</div>
<div className="awardMovieInfo">
<p>{movie.Title}</p>
<p>year {movie.Year}</p>
</div>
<div className="withdrawButton" onClick={(e) => this.deleteMovie(e, movie)}> WITHDRAW </div>
</div>
)
})}
</div>
<Popup />
</>
)
} else { ...
This is my banner structure.
<div id="popup1" className="overlay">
<div className="popup">
<h2>Here i am</h2>
<a className="close" href="#">×</a>
<div className="content">
<p>Congratulations. You've nominated 5 movies.</p>
<button onClick={this.closeBanner}>Try again</button>
</div>
</div>
</div>
This is my css for the banner element.
.overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
transition: opacity 500ms;
visibility: hidden;
opacity: 0;
}
How can I dynamically change element's styles using conditions to render that element?
You're trying to access your Popup component before it gets created. In other words, this.showBanner() is called before <Popup /> is rendered.
One solution is to move your popup to a higher-level component
This might be a good use case for React Context, which will allow you to have some global state that your components can tap into without having to pass the banner state through multiple components as props.
If you are going to do this, you might consider not manually updating the styling with querySelectors; instead, you can have React either render or not render the component based on your global banner state.
Your application will be wrapped in <BannerContext.Provider> tags, and then the component that needs to render or not render the banner can use <BannerContext.Consumer> tags to check the current banner state. You can also store a toggle function in the BannerContext so that other parts of the application (and the banner itself) can toggle the BannerContext as needed.
Related
I'm currently creating my custom implementation of a modal. All works perfectly fine so far but I can't seem to animate it and I can't get my head around it.
This is my Modal component
import React from 'react'
import Slider from './Slider'
import {IoIosCloseCircleOutline} from "react-icons/io"
import styled from "styled-components";
export default function Modal(props) {
const Modal = styled.div `
transform: translateX(${({animateSlideInRight}) => (animateSlideInRight ? "0" : "100vw")});
transition: transform 1s;
width: 1000px;
height: 650px;
z-index: 100;
position: fixed;
background: white;
transition: all 1.1s ease-out;
box-shadow:
-2rem 2rem 2rem rgba(black, 0.2);
visibility: visible;
display: flex;
border-bottom-right-radius: 100px;
`
const closeModal = () => {
props.setShow(false)
}
const data = props.data
if (!props.show) {
return null
}
return (
<div className="modalWrapper">
<Modal className="modal" id="modal" animateSlideInRight = {props.show}>
<div className="modalHeaderWrapper">
<IoIosCloseCircleOutline className="modalCloseCross" onClick={closeModal}/>
<img src={data[0].logo} alt="logo" />
<h2>{data[0].title}</h2>
</div>
<div className="modalRightFlex">
<Slider
images={[data[0].image1Carrousel, data[0].image2Carrousel, data[0].image3Carrousel]}
/>
<div className="modalRightDescription">
<h1>Description</h1>
<p>{data[0].description}</p>
<h1>Technologies</h1>
<div className="modalTechnologiesWrapper">
{data[0].technologiesUsed.map((image) => {
return <img src={image}/>
})}
</div>
</div>
</div>
</Modal>
</div>
)
}
As you see my modal is a styledComponent that defines whether to translate in X or not depending on the show state. In my scenario I had to lift up state since I'm opening this modal from clicking on a card which in itself is a different component, so their ancestor is taking care of the states.
My current CSS for modal is as seen in the styled div.
Things I have tried
1-tried having a regular div and handle the animation through CSS with keyframes --> It works for sliding in but it doesn't when I close (in that instance I had my show state defining a class name for the modal with a different animation for each of them)
2-tried to set a animate state and define the className based on whether that state is true or false. It works the first time when I close (despite having to introduce a timeout of the animation duration between setting animate to false and show to false) but then it goes bonkers and starts flickering everywhere.
Anyway someone can see the issue? Many thanks
edit
Sanbox link: https://codesandbox.io/s/trusting-shape-vxujw
You should define Modal in the outer scope of the component rendering it, the animation does not complete and you resetting it by redefining it on the next render.
Also resetting an animation should be done with none instead of giving an actual length.
Moreover, there might be more CSS bugs related that can hide your modal animation like z-index and position, if your question is focused on an animation problem you should remove all the noise around it.
See working example:
const Animation = styled.div`
transform: ${({ animate }) => (animate ? "none" : "translateX(500px)")};
transition: transform 1s;
`;
function Modal(props) {
return <Animation animate={props.show}>hello</Animation>;
}
function Component() {
const [show, toggle] = useReducer((p) => !p, false);
return (
<>
<Modal show={show} />
<button onClick={toggle}>show</button>
</>
);
}
Also, you shouldn't return null when you don't want to animate, you will lose the close animation.
// remove this code
if (!props.show) {
return null;
}
I want to make a sticky footer that will stick to the bottom if there's not enough content to fill up the whole page. I've searched up ways to do it in CSS, but a lot of them doesn't translate to React/Next since it involves messing with the html and body tag. I'm wondering if there are other ways to do it.
Here is the JSX for my Footer:
<div>
<footer>
<a href={"https://www.instagram.com/linghandmade18/"}>
<i className="fab fa-instagram" />
</a>
</footer>
<h2>Some Text</h2>
</div>
Here is my Layout file for Next.js:
const Layout = (props) => {
return (
<div>
<Navbar />
{props.children}
<Footer />
</div>
);
};
If you don't want to mess with html and body tags then you need a container on which you can apply your style. So first of all create a common container inside your Layout (add container class to parent element), like this:
const Layout = (props) => {
return (
<div class="container">
<Navbar />
{props.children}
<Footer />
</div>
);
};
Now you have a .container class to the div which is parent div of Navbar, Content and Footer. Now add following styles to the container class:
.container {
min-height: 100vh;
position: relative;
}
Because of this your container height will stay at least 100vh (viewport height), it will grow more if content length increases.
And for your footer component, make these changes if the h2 is part of the footer (for better accessibility).
const Footer = (
<footer>
<a href={"https://www.instagram.com/linghandmade18/"}>
<i className="fab fa-instagram" />
</a>
<h2>Some Text</h2>
</footer>
);
For footer styling you can add this style:
footer {
position: absolute;
bottom: 0;
left: 0;
}
This way it will always stay in the bottom position regardless of the content height.
I have the following modal component:
export default function LoginModal(props) {
const { showLogin, hideLogin } = props
if (!showLogin) return null
return (
<div class='overlay'>
<div class='modal'>
<article class='mw5 center bg-white br3 pa3 pa4-ns mv3 ba b--black-10'>
<div class='tc'>
<h1 class='f4'>Firstname Lastname</h1>
<hr class='mw3 bb bw1 b--black-10' />
</div>
<p class='lh-copy measure center f6 black-70'>
test test test test
</p>
</article>
</div>
</div>
)
}
which I am attempting to conditionally render using a state property stored in my redux store. However, when I place it as follows:
<article class='pv6 center ph3 ph5-ns tc br2 bg-washed-green dark-green mb5'>
PAGE CONTENT HERE
</article>
<LoginModal />
It appears below the rest of the screen content rather than above everything else as I had hoped. I am using the following css to try to get this effect, but it doesn't seem to be working:
.overlay {
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: 'rgba(0,0,0,0.3)';
padding: 50;
z-index: 300;
}
.modal {
background-color: '#fff';
border-radius: 5;
max-width: 500;
min-height: 300;
margin: '0 auto';
padding: 30;
}
I need the modal to appear as a login card in the center of the screen with the background dimmed, as is common on many websites. I would also rather not use a UI component library to achieve. Thanks for the help!
I think you are missing a position: fixed in your .overlay styles.
Also, you might want to consider using Portals to render the modal outside the DOM hierarchy of your App, that is, outside the <div id="app"></div> or <div id="root"></div> we commonly use.
To do that, your index.html or equivalent file would have something like this:
<div id="app"></div>
<div id="modal"></div>
And then you need to update your LoginModal like this:
export default function LoginModal(props) {
const { showLogin, hideLogin } = props;
if (!showLogin) return null;
return ReactDOM.createPortal((
<div class='overlay'>
<div class='modal'>
<article class='mw5 center bg-white br3 pa3 pa4-ns mv3 ba b--black-10'>
<div class='tc'>
<h1 class='f4'>Firstname Lastname</h1>
<hr class='mw3 bb bw1 b--black-10' />
</div>
<p class='lh-copy measure center f6 black-70'>
test test test test
</p>
</article>
</div>
</div>
), document.getElementById('modal'));
}
Note that if you try to reuse the code in this example to create a generic Modal component, you would only be able to show one at a time, as they would be rendering inside the same #modal element.
In the Portals documentation you can see an example that creates a new element dynamically so that you can have multiple modals at the same time.
Inside a React component, I want to be able to calculate the combined height of each of my child containers, in this case, the three h3 elements, so that I can calculate the exact height of my parent div when animating the transition of its height. I have seen examples of this with basic js as well as using component mounting methods, but how do I get the height of these elements using just JavaScript?
class Div extends React.Component {
render() {
return (
<div>
<h3>Description One</h3>
<h3>Description Two</h3>
<h3>Description Three</h3>
</div>
);
}
}
ReactDOM.render(<Div />, app);
div {
background-color: pink;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="app"></div>
In React, to manipulate the DOM, you'll want to use Refs: React Docs - Adding a Ref to a DOM element.
In your example, for your <h3> elements, you'd do something like this:
<h3 ref={(elem) => this.Line1 = elem}>Description One</h3>
<h3 ref={(elem) => this.Line2 = elem}>Description Two</h3>
<h3 ref={(elem) => this.Line3 = elem}>Description Three</h3>
Once you have a Ref to an element, you can use methods & properties of any DOM element on it. For instance, to get its height, you can use Element.clientHeight:
calcHeight() {
return this.Line1.clientHeight + this.Line2.clientHeight + this.Line3.clientHeight;
}
Working JSFiddle
I have dynamic number of buttons I need to render depending on the user. During this dialog box, they need to select one of the options and submit. my goal is to make it look like this, showing at most 4 buttons, with the ability to scroll through the rest:
However, if there are more than 4 buttons available, the buttons go offscreen and become impossible to access, even if the user scrolls down the page outside of the dialog box:
I would like to restructure my code so that I have a react component limited in size to only show 4 at once, ensuring the entire screen stays on the page.
I have stored my code at this JSFiddle: https://jsfiddle.net/connorcmu/f01xhsat/1/
renderDialog and renderButtons are the relevant sections here:
renderButtons: function() {
var accountList = this.props.accounts;
var buttonList = accountList.map(function(account) {
return (<div className='col-sm-6'>
<GEMSelector classname='leftButtonContainer' header={account.organization_name} stat={account.tier} contacts={account.subscriber_count+' / '+account.max_subscribers+' Contacts'} credits={account.mailing_credits + ' Credits'}></GEMSelector>
</div>);
});
var accountsGrid =
(<div className="container-fluid">
<div className="row">
<div className='col-sm-6'>
<GEMSelector classname='leftButtonContainer' header='FRANKS CASINO' stat='Create new account' contacts='' credits='' specialpadding={true}></GEMSelector>
</div>
{buttonList}
</div>
</div>);
return {accountsGrid};
}
Also, if there is anyway to make the dialog box bigger so that the submit buttons just don't float like that, that would be very helpful too.
From the code it looks like you need to add a new class for the className="row" in accountsGrid .
var accountsGrid =
(<div className="container-fluid">
<div className="row selection-area">
See that a new class is added 'selection-area' and add overflow with width twice the height of the 'GEMSelector'
.selection-area{
overflow: scroll;
height: 300px;
}
Had the same problem in React JS. I would add a container class, like:
<div class="row container"</div>
and then add styling to that container class:
.container {
overflow-y: scroll;
height: 100vh;
}