Can you render react component to div with display none? - javascript

I need to render react component to div with id hover-countdown. So as the name says it is not visible when page load, but only on hover.
Does this somehow affect ReactDOM render? Because i am not able to render it.
Is there some other way?
Thank you:)
const HoverCountdown = () => {
return (
<span>Countdown</span>
);
};
document.addEventListener("DOMContentLoaded", () => {
ReactDOM.render(
<HoverCountdown />,
document.getElementById("hover-countdown")
);
});

Does this somehow affect ReactDOM render?
Not really. It React only concerns itself with DOM updates.
If the element it renders inside is display: none then the React rendering process will generate the DOM inside that element.
Then, subsequent to the React rendering, the browser will convert the DOM to something rendered on screen (which will be nothing because the container is display: none).

If you set the visibility to hidden, hovering over the invisible component will not trigger the :hover styles to take effect.
What is odd is that if you inspect element and toggle the :hidden styles then it will properly show up.
const HoverCountdown = () => (
<span>Countdown</span>
);
//document.addEventListener('DOMContentLoaded', () => {
ReactDOM.render(
<HoverCountdown />,
document.getElementById('hover-countdown')
);
//});
.wrapper {
background: #EFE;
}
#hover-countdown {
visibility: hidden; /* will render fine if removed */
}
#hover-countdown:hover {
visibility: visible;
color: #040;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div class="wrapper">
<div id="hover-countdown"></div>
</div>

Related

Deeper understanding of React event bubbling / propagation and state management

I apologise in advance if this is a silly question. Although I have managed to get it to work, I would like to get a deeper understanding.
I am building a custom hamburger menu in react which closes whenever you click anywhere outside the unordered list or the hamburger icon itself.
I have seen answers here Detect click outside React component
And I have followed it but I couldn't understand why it wasn't working.
Firstly when it was just the hamburger icon and no click outside the menu to close option, it worked perfectly.
Then when I used the useRef hook to get a reference to the unordered list in order to only close the menu when the list is not clicked, it worked perfectly except for when I clicked the actual hamburger icon.
After a lot of amateur debugging I finally realised what was happening.
First when I opened the menu the state showMenu changed to true,
Then when I clicked the hamburger icon to close,
The parent wrapper element was firing first instead of the hamburger menu which is strange as during the bubbling phase I would expect the inner most element to fire first.
So the parent element would close the menu changing the state, causing the components to re-render. Then when the event would reach the actual icon the handleClick would once again toggle the state to true giving the impression that the hamburger click isn't working.
I managed to fix this by using event.stopPropogation() on the parent element.
But this seems very strange because I would not expect the parent element's click to fire first especially when Im using bubbling phase.
The only thing I can think of is because it is a native dom addeventlistener event it is firing first before the synthetic event.
Below is the code for the Mobile navigation which has the hamburger
The header component renders the normal Nav or the MobileNav based on screen width. I haven't put code for the higher order components to make it easier to go through, but I can provide all the code if needed:
//MobileNav.js
export default function MobileNav() {
const [showMenu, setShowMenu] = useState(false);
const ulRef = useRef();
console.log('State when Mobilenav renders: ', showMenu);
useEffect(() => {
let handleMenuClick = (event) => {
console.log('App Clicked!');
if(ulRef.current && !ulRef.current.contains(event.target)){
setShowMenu(false);
event.stopPropagation();
}
}
document.querySelector('#App').addEventListener('click', handleMenuClick);
return () => {
document.querySelector('#App').removeEventListener('click', handleMenuClick);
}
}, [])
return (
<StyledMobileNav>
<PersonOutlineIcon />
<MenuIcon showMenu={showMenu} setShowMenu={setShowMenu} />
{
(showMenu) &&
<ul ref={ulRef} style={{
backgroundColor: 'green',
opacity: '0.7',
position: 'absolute',
top: 0,
right: 0,
padding: '4em 1em 1em 1em',
}}
>
<MenuList/>
</ul>
}
</StyledMobileNav>
)
}
//MenuIcon.js
/**
* By putting the normal span instead of the MenuLine component after > worked in order to hover all div's
*/
const MenuWrap = styled.div`
width: 28px;
position: relative;
transform: ${(props) => props.showMenu ? `rotate(-180deg)` : `none` };
transition: transform 0.2s ease;
z-index: 2;
&:hover > div{
background-color: white;
}
`;
const MenuLine = styled.div`
width: 100%;
height: 2px;
position: relative;
transition: transform 0.2s ease;
background-color: ${(props) => props.showMenu ? 'white' : mainBlue};
&:hover {
background-color: white;
}
`;
const TopLine = styled(MenuLine)`
${(props) => {
let style = `margin-bottom: 7px;`;
if(props.showMenu){
style += `top: 9px; transform: rotate(45deg);`;
}
return style;
}}
`;
const MidLine = styled(MenuLine)`
${(props) => {
let style = `margin-bottom: 7px;`;
if(props.showMenu){
style += `opacity: 0;`;
}
return style;
}}
`;
const BottomLine = styled(MenuLine)`
${props => {
if(props.showMenu){
return `bottom: 9px; transform: rotate(-45deg);`;
}
}}
`;
export default function MenuIcon({showMenu, setShowMenu}) {
const handleMenuClick = (event) => {
console.log('Menu Clicked!');
console.log('State before change Icon: ', showMenu);
setShowMenu(!showMenu);
}
return (
<MenuWrap onClick={handleMenuClick} showMenu={showMenu}>
<TopLine onClick={handleMenuClick} showMenu={showMenu}></TopLine>
<MidLine onClick={handleMenuClick} showMenu={showMenu}></MidLine>
<BottomLine onClick={handleMenuClick} showMenu={showMenu}></BottomLine>
</MenuWrap>
)
}
Reading this article https://dev.to/eladtzemach/event-capturing-and-bubbling-in-react-2ffg basically it states that events in react work basically the same way as DOM events
But for some reason event bubbling is not working properly
See screenshots below which show how the state changes:
Can anyone explain why this happens or what is going wrong?
This is a common issue with competing event listeners. It seems you've worked out that the problem is that the click out to close handling and the menu button click to close handling are both triggered at the same time and cancel each other out.
Event listeners should be called in the order in which they are attached according to the DOM3 spec, however older browsers may not implement this spec (see this question: The Order of Multiple Event Listeners). In your case the click out listener (in the <MobileNav> component) is attached first (since you use addEventListener there, while the child uses the React onClick prop).
Rather than relying on the order in which event listeners are added (which can get tricky), you should update your code so that either the triggers do not happen at the same time (which is the approach this answer outlines) or so that the logic within the handlers do not overlap.
Solution:
If you move the ref'd element up a level so that it contains both the menu button and the menu itself you can avoid the overlapping/competing events.
This way the menu button is within the space where clicks are ignored so the outer click listener (the click out listener) won't be triggered when the menu button is clicked, but will be if the user clicks anywhere outside the menu or its button.
For example:
return (
<StyledMobileNav>
<PersonOutlineIcon />
<div ref={menuRef}>
<MenuIcon showMenu={showMenu} setShowMenu={setShowMenu} />
{ showMenu && (
<ul>
<MenuList/>
</ul>
)}
</div>
</StyledMobileNav>
)
Then use menuRef as the one to check for clicks outside of.
As an additional suggestion, try putting all the menu logic into a single component for better organization, for example:
function Menu() {
const [showMenu, setShowMenu] = React.useState(false);
// click out handling here
return (
<div ref={menuRef}>
<MenuIcon showMenu={showMenu} setShowMenu={setShowMenu} />
{ showMenu && (
<ul>
<MenuList/>
</ul>
)}
</div>
)
}

How to select sibling element with React?

React Component
import React, {useState} from 'react';
import './CounterButton.css';
const CounterButton = (props)=>{
const [currentCount, setCurrentCount] = useState(0);
const handleClick = (event)=>{
if(currentCount == 9){
event.target.classList.toggle('bound-hit');
}
setCurrentCount(currentCount+props.incrementVal);
};
return (
<div class="count-container">
<button onClick={handleClick}>+{props.incrementVal}</button>
<p>{currentCount}</p>
</div>
);
};
export default CounterButton;
External stylesheet for this component
.count-container {
display: flex;
padding: 10px;
}
.count-container > button {
width: 50px;
margin: 0 10px;
}
.bound-hit {
color: red;
}
I have a react component and stylesheet for that component. In this case it toggle class bound-hit to the classList of button. I could select button using event.target and but I want to toggle this class to the <p></p> tag inside my div. My question is how can I select that p tag using event. p tag is like a sibling of button. div with class count-container is parent. I can also select parent div by event.target.parent but I want to select p tag and toggle class bound-hit to that.. How can I do that?
I don't think you need a React specific answer here.
In vanilla JS you can use the nextElementSibling method.
const handleClick = (event) => {
const p = event.target.nextElementSibling
}
Or instead you can do it in CSS with the adjacent sibling combinator.
.bound-hit + p {
// apply styles to the <p> that's just after .bound-hit in the DOM
}
However, if you "manually" add a class in a react component (meaning that this class gets added to the DOM without any representation in the state), some virtual DOM reconciliations might end up removing it.
In a lot of cases, this won't be a problem, but if it is, then you should use a state for it. Here's a simplified example of what that would look like:
const [pClass, setPClass] = useState('')
const handleClick = () => {
setPClass('bound-hit')
}
return (
<p className={pClass} />
)
The question shouldn't be "how to select a sibling" but "how to assign CSS class to the P element on [condition]".
If a React component directly has ownership over the (child) elements you can simple change the components state and apply it to the class list of the element using className.
Doing any DOM manipulation/traversing within a component is mainly bad form using React and overcomplicates the solution.
const CounterButton = (props)=>{
const [currentCount, setCurrentCount] = useState(0);
const [currentClass, setCurrentClass] = useState();
const handleClick = (event)=>{
if(currentCount == 9){
setCurrentClass('bound-hit');
}
setCurrentCount(currentCount+props.incrementVal);
};
return (
<div class="count-container">
<button onClick={handleClick}>+{props.incrementVal}</button>
<p className={currentClass}>{currentCount}</p>
</div>
);
};

How to blur/disable parent component when child page (popup page) is opened?

I am working with React. I want to know about the easiest way on how to to blur parent component when child page (popup page) is opened.
Here is my Fiddle Workspace Demo
Even if I blur it the parent should still be clickable
Can anyone show how to achieve this functionality?
We can use CSS to blur the parent component. Something like this:
We will wrap the child component into a div with the class name as overlay
Apply CSS effect to blur the parent component
[Note: You can also add the click event to the blur areas by adding the event to the overlay div]
{this.state.childOpen && (
<div className="overlay">
<div className="overlay-opacity" />
<Child data={data} applyFilter={this.applyFilter} />
</div>
)}
.overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.overlay-opacity {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: black;
opacity: 0.5;
}
Here is the live demo
To hide the child component onclick of the blur area, we can add a event like this
hideChild() {
this.setState({
childOpen: false
});
}
Here is the live demo
Hope it helps :)
Since you are working on react, I would like to suggest you to use react-responsive-modal library. You can install this using npm.
It is easily customizable via props.
Please find the example below:
https://codesandbox.io/s/9jxp669j2o
What i understood is that you want to blur the table when child is opened
Here is code you need to add
formatStyle = () => {
if (this.state.childOpen == true) return { filter: "blur(5px)" };
else return {};
};
//add formatStyle in main app
styleData={this.formatStyle()} // in props on table
style = {this.props.styleData} //add this attribute in main container of table component
styleData={this.formatStyle()} // Pass this in ReactTable in main app
The feature you are looking for is already in Semantic-UI-React Modal. This is very easy to use, you only have to pass some props to get above result.
demo https://react.semantic-ui.com/modules/modal/#variations-dimmer

What do React Portals solve?

From the docs of react portal:
A typical use case for portals is when a parent component has an overflow: hidden or z-index style, but you need the child to visually “break out” of its container. For example, dialogs, hovercards, and tooltips.
The suggested solution is to use:
// this line is rendered in `Portal1` component,
// which is rendered in `Parent1` component.
ReactDOM.createPortal(Child1, Container1)
I don't understand what does it solves. Why making Child1 child of Container1 instead of Parent1 helps?
My question maybe not clear so if it doesn't -> How does this solution differ from other solutions for creating "dialogs, hovercards, and tooltips"?
When you initialise a React application, ReactDOM tells one DOM container that all its React components will be rendered under this DOM. This makes React do all rendering processing.
Sometimes you need to control a React Component to render as a child to a different DOM element, and continue to interact with your React application. This is why we use React Portals
As React creates virtual elements under the hood, you cannot convert the into DOM elements and insert them directly into the DOM. React Portals allows to you pass a React Elements and specify the container DOM for the React Element
Here is an example:
You have a Modal component which renders a div element in the center.
function Modal() {
return (
<div style={{ position: 'absolute', left: '50%'}}>
Message
</div>
);
}
One puts your Modal component inside a div of relative position.
<div style={{ position: 'relative', left: 100 }}>
<Modal />
</div>
The problem is when Modal component is rendered, its position is relative to parent div's position but you need to show it at the centre of window.
In order to solve this problem, you can append your Modal component directly to the body element with a portal
Here is the solution with Portals.
function ModalRenderer() {
return (
React.createPortal(
<Modal />,
document.body
)
);
}
And use ModalRenderer component anywhere inside your application.
<div style={{ position: 'relative', left: 100 }}>
<ModalRenderer />
</div>
ModalRenderer has the container element for the Modal which is outside of the DOM tree, but still within the React Application tree
In React V15,we can only add children dom into the father dom.That means, if you want to have an element, you have to create a new div.Like this:
<div>
{this.props.children}
</div>
In React v16,don't need to create a new div.We can use portal to add the children element to any dom in the dom tree.
ReactDOM.createPortal(
this.props.children,
domNode
);
overflow: hidden or z-index style
If a parent component has an overflow: hidden or z-index style, and the children element type is dialogs, hovercards, tooltips and so on,these should be on the upper layer of the father element, meaning break out.But they maybe shade by the father component.
So createPortal offers a better option.It can load on the upper component of the father component.After mounting the element to another dom,it won't be sheltered.
Event and bubble up
Even the component mounted on another component, event can budde up to the father component.
One good case is to separate CSS concerns.
Here is an example:
HTML
<div id="app-root"></div>
<div id="modal-root"></div>
CSS
.app {
position: fixed;
height: 100%;
width: 100%;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.modal {
background-color: rgba(0,0,0,0.5);
}
Babel
const appRoot = document.getElementById('app-root')
const modalRoot = document.getElementById('modal-root')
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showModal: false
}
this.handleShow = this.handleShow.bind(this);
this.handleHide = this.handleHide.bind(this);
}
handleShow() {
this.setState({
showModal: true
})
}
handleHide() {
this.setState({
showModal: false
})
}
render() {
const modal = this.state.showModal ? (
<Modal>
<div className="modal">I am no longer centered!</div>
</Modal>
) : null
return (
<div className='app'>
Basic
<button onClick={this.handleShow}>
Show Modal
</button>
{modal}
</div>
)
}
}
class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount(){
modalRoot.appendChild(this.el)
}
componentWillUnmount() {
modalRoot.removeChild(this.el)
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el
)
}
}
ReactDOM.render(<App />, appRoot)
Observations:
1. The main text has fixed position and is centered
2. When we click the button, the text popped up. Notice the text is no longer centered!
3. If we had used it to something like this:
<div className='app'>
Basic
<button onClick={this.handleShow}>
Show Modal
</button>
<div className="modal">I am centered ;(</div>
</div>
Notice the text is centered. Modal works by going to modal-root and attach that DOM element there. You can have your own CSS under modal-root hood, separate from parent component (app-root).
You are now no longer obligated to attach your "child" components under your parent's. In this case, you (app-root) are attaching it to its sibling (modal-root). You can totally attach it to document.body, or whatever element you wanted to. Another perk is, like other user mentioned, event bubbling happens as if that child component is their own child.
A Parent component in #app-root would be able to catch an uncaught, bubbling event from the sibling node #modal-root.
Source

Styled Component dynamic tag name

I want to make a dynamic component. (the dynamic TAG will be a styled component -> emotion)
const Dynamic = ({ tag: Tag, children, ...rest }) =>
<Tag {...rest}>
{ children }
</Tag>
The component will be a styled component like:
const Column = styled(div)({ color: 'red' })
const Row = styled(span)({ color: 'yellow' })
This looks all nice, and working properly, BUUUUUT:
When I try use a DynamicComponent inside another DynamicComponent:
<DynamicComponent tag={Row}>
{
mapOver.map(item=>
<DynamicComponent tag={Column}/>
)
}
</DynamicComponent>
then for some reason the Dynamic children will use the Dynamic Parent's style.
Is there anything I missing?
P.S.:
If instead of using dynamic styles, I do something like this:
<Row>
<Column/>
</Row>
then the styles, classNames, styled tags, are applied properly.
To make it a little more clear:
As you can see the DynamicComponent's will use the parent's styles, classNames, styled tags... (NOT THE BEHAVIOUR I WOULD EXPECT)
Below example creating a dynamic tag name for a styled-component:
// All headings use the same styled-component "Heading" function
const StyledHeading = styled.div`
font-size: ${({level}) => 4/level }em; // <- dynamic font size
font-weight: 300;
margin: 0;
`
// the trick here is the "as={...}" to create dynamic tag name
const Heading = ({level = 1, children}) =>
<StyledHeading as={`h${level}`} level={level}>
{children}
</StyledHeading>
ReactDOM.render([
<Heading>Hello, world!</Heading>,
<Heading level={2}>Title 2</Heading>,
<Heading level={3}>Title 3</Heading>
] ,document.body
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/styled-components#4.4.1/dist/styled-components.min.js"></script>
Reference:
"as" prop documentation
There is a misunderstanding in the usage of styled-components as a tag is intended as HTML tag (input, div and so on). The best way is to define a StyledRow and a StyledColumn separately and use them with appropriate names. This will help also to make your code more readable.

Categories

Resources