I want to show different data or hide component if it doesn't fit on screen.
I made a working example but i don't think it's a right/elegant way to do it.
Maybe someone can show me a better way?
This image of example that i made in codepen.
1. I want to hide first red block if it's doesn't fit in grey.
I don't want to do it on media queries of window-size because my red blocks maybe be different in size from user to user.
Codepen example (resize to hide block): https://codepen.io/bofemptiness/pen/YJRKGj
const styled = styled.default
const Component = React.Component;
const DivHat = styled.div`
position: fixed;
top: 0;
width: 100% ;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
background-color: blue;
`
const DivProfile = styled.div`
display: flex;
height: 100%;
cursor: pointer;
`
const ExpCooDiv = styled.div`
display: flex;
flex-direction: column;
font-size: 1rem;
`
class App extends Component {
constructor(props) {
super(props)
this.state = { showCookies: true, lvlRefLastSize: 0 }
this.statsRef = React.createRef()
this.cocRef = React.createRef()
this.lvlRef = React.createRef()
this.mediaStats = this.mediaStats.bind(this);
}
componentDidMount() {
// Add listner when window resizes
window.addEventListener('resize', this.mediaStats)
// Activate function at least one time on load
this.mediaStats()
}
componentWillUnmount() {
window.removeEventListener('resize', this.mediaStats)
}
// Show/hide first red block if summ of red blocks widths <= width of grey one
mediaStats = () => {
console.log(this.statsRef.current.scrollWidth)
if (this.lvlRef.current.scrollWidth != 0)
this.setState({ lvlRefLastSize: this.lvlRef.current.scrollWidth })
if (this.statsRef.current.scrollWidth <= this.state.lvlRefLastSize + this.cocRef.current.scrollWidth) {
this.setState({ showCookies: false })
} else {
this.setState({ showCookies: true })
}
}
render () {
return(
<DivHat>
<div>Menu</div>
<div id='test' ref={this.statsRef}>
<div ref={this.lvlRef} id='test2'>
{this.state.showCookies &&
<React.Fragment>
<span>DATA that i hide</span>
</React.Fragment>
}
</div>
<div ref={this.cocRef} id='test2'>
ANOTHER DATA
</div>
</div>
<DivProfile >
<div> Profile </div>
</DivProfile>
</DivHat>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
Related
Hi the following code should modify the visibility of a react component depending on whether or not a button is clicked, this when a button is clicked the first element must disappear and I have to appear the second, but this does not happen what is this due to?
React code:
import React from "react";
import styled from "styled-components";
import image from "../../assets/img/calc.png";
import CalculatorContainer from "./CalculatorDraggable/index";
class DraggableCalculator extends React.Component {
constructor(props) {
super(props);
this.state = { isCalculatorVisible: false };
}
enable() {
console.log("Make calculator visibile");
this.setState({
isCalculatorVisible: true,
});
}
render() {
return (
<p>
{this.state.isCalculatorVisible == true ? (
<CalculatorContainer />
) : (
<p></p>
)}
<Container onClick={this.enable}>
<img
key={Math.random()}
src={image}
alt="help"
width={120}
height={220}
style={{ marginLeft: 20 }}
/>
</Container>
</p>
);
}
}
export default DraggableCalculator;
const Container = styled.div`
border-radius: 25px;
border: 2px solid #73ad21;
padding: 20px;
width: 200px;
height: 200px;
/* background-color: var(--cLight2);
border: 5px solid var(--cMain); */
border-radius: 50%;
margin-left: auto;
margin-top: 30px;
margin-right: auto;
cursor: pointer;
// #MEDIA TABLET LANDSCAPE
#media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) {
width: 100px;
height: 100px;
}
img {
max-width: 100%;
max-height: 100%;
}
`;
It looks like the enable function doesn't have the this of the class component bound to it, it throws an error TypeError Cannot read properties of undefined (reading 'setState').
Either bind this to it in the constructor
constructor(props) {
super(props);
this.state = { isCalculatorVisible: false };
this.enable = this.enable.bind(this); // <--
}
or convert to an arrow function so it happens automatically
enable = () => {
console.log("Make calculator visible");
this.setState({
isCalculatorVisible: true,
});
}
Your code is not working because "this" is undefined inside the enable() function. It so because React component (DraggableCalculator, in your case) does not auto-bind its methods to itself.
Hence you have to bind any method yourself inside the constructor, like this for your case:
constructor(props) {
super(props);
this.state = { isCalculatorVisible: false };
this.enable = this.enable.bind(this);
}
The question is, How do I call a method from a child component? Ex : Consider I have a login form component located in the Parent component. So I need to show that form when I click the login button. The function to show the login form will be written in the Parent component and I need to t that function when I click the Button located in a child component.
This is the Parent component 👇
import Nav from './componets/navigation-bar.js'
import Comp from './componets/footer.js'
import UserComp from './componets/user-comp.js'
import Base from './componets/Base.js'
const style = `
.container {
display: flex;
flex-direction: row;
justify-content: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.container > user-comp {
padding: 1em;
}
`
const content = `
<navigation-bar></navigation-bar>
<div class="container">
<user-comp mirror="true">
<img slot="image" src="https://www.zricks.com/img/UpdatesBlog/44b94c9d-ab13-401d-9e5b-86a00f9da6496%20Must%20Follow%20Tips%20to%20Market%20a%20Luxury%20Home.jpg" alt="Image"></img>
<h1 slot="title">Rent or Lease your own property</h1>
</user-comp>
<user-comp mirror="true">
<img slot="image" src="https://s3.amazonaws.com/clients.granalacantadvertiser.images/wp-content/uploads/2017/06/14072232/2236775_2_O.jpg" alt="Image"></img>
<h1 slot="title">Looking for a place</h1>
</user-comp>
</div>
<footer-c></footer-c>
`
export default class UI extends Base {
constructor() {
super()
this.render(style, content)
this.attachShadow({ mode: 'open' })
this.shadowRoot.appendChild(this.template.content.cloneNode(true))
}
clicked = () => {
console.log('clicked')
}
}
window.customElements.define('ui-c', UI)
document.querySelector('#root').innerHTML = '<ui-c></ui-c>'
This is the child component 👇
import Base from './Base.js'
const style = `
header {
position: absolute;
top:0;
left:0;
right:0;
background-color: #111111;
color: #eeeeee;
z-index:1;
}
.logo {
margin-left: 2em;
}
nav {
display: flex;
flex-direction: row;
justify-content: space-between;
}
#login-button {
height: 2.5em;
width: 10em;
margin: auto 2em;
text-transform: uppercase;
color: #eeeeee;
background-color: #239710;
border: none;
box-shadow: 1px 1px 5px 1px rgba(23,97,10,0.64);
outline: none;
cursor: pointer;
transition: 0.4s;
}
#login-button:hover {
background-color: #34a832;
}
`
const content = `
<header>
<nav>
<h3 class="logo">Homey</h3>
<button id="login-button"> login </button>
</nav
</header>
`
export default class Nav extends Base {
constructor() {
super()
this.render(style, content)
this.attachShadow({ mode: 'open' })
this.shadowRoot.appendChild(this.template.content.cloneNode(true))
}
connectedCallback() {
this.shadowRoot
.querySelector('#login-button')
.addEventListener('click', clicked())
}
}
window.customElements.define('navigation-bar', Nav)
This is the Base class was written by me (In case to understand) 👇
export default class Base extends HTMLElement {
template = document.createElement('template')
style(style) {
if (style === null) return ' '
return '<style>' + style + '</style>'
}
render(style, content) {
if (content === null) content = ''
this.template.innerHTML = this.style(style) + content
}
}
You can pass data from your child component to parent component using an event.
Within your child component you can create a custom event and fire it up when you want to call the method on parent component.
// create and dispatch the event
var event = new CustomEvent("cat", {
detail: {
hazcheeseburger: true
}
});
obj.dispatchEvent(event);
Then in your parent component you can listen for that event. Once the event is triggered, the event listener will catch and proceed accordingly. It will look something like this.
obj.addEventListener("cat", function(e) { process(e.detail) });
Example is taken from MDN web docs.
Events are a great solution to prevent tight coupling between components. But require some work.
Sometimes you just know you need that DIV element 3 levels up/down the DOM
UP the DOM
The standard element.closest(selector) "walks" up the DOM to find the selector you are after.
https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
But .closest() does not escape shadowDOM
For that you have to write a recursive closestNode(selector) function that crosses all shadowDOMs with .getRootNode() till it finds the selector
customElements.define("my-element", class extends HTMLElement {
closestNode(
selector, // selector like in .closest()
start = this, // extra functionality to skip a parent
closest = (el, found = el && el.closest(selector)) =>
!el || el === document || el === window
? null // standard .closest() returns null for non-found selectors also
: found || closest(el.getRootNode().host) // recursion!! break out to parent DOM
) {
return closest(start); // look from start
}
connectedCallback() {
this.attachShadow({
mode: 'closed'// just to show it works with closed mode
}).append(document.getElementById(this.nodeName).content.cloneNode(true));
this.onclick = (evt) => {
evt.stopPropagation();
let container = this.closestNode('div');
let color = evt.target.childNodes[0].nodeValue;
container.style.background = color;
}
}
})
<template id=MY-ELEMENT>
<style>
button {
font: 16px Arial, sans;
margin:.5em;
}
button:hover{
background:lightgreen;
}
</style>
<button><slot></slot></button>
</template>
<div>
<my-element>red
<my-element>green
<my-element>orange
<my-element>blue
<my-element>yellow
<my-element>hotpink
</my-element>
</my-element>
</my-element>
</my-element>
</my-element>
</my-element>
</div>
DOWN the DOM
Something you want to prevent, but sometimes comes in handy
const shadowDive = (
el,
selector,
match = (m, r) => console.warn('match', m, r)
) => {
let root = el.shadowRoot || el;
root.querySelector(selector) && match(root.querySelector(selector), root);
[...root.children].map(el => shadowDive(el, selector, match));
}
I have been trying to make the links in my sticky nav bar highlight when on the corresponding section.
I have been able to get it to partially work using JavaScript with all links being highlighted when scrolling over the corresponding sections, however, the section 1 link is highlighted even if I'm on the landing page and have not scrolled at all. How can I make it so that section 1 isn't highlighted until I scroll to the section?
I've tried to use an if statement to set all links to not active if window.scrollY === 0, but this only works for the initial render and doesn't work after I have scrolled down and back.
Update
Adding another if statement at the start of the highlightNav function to check if window.scrollY === 0 has allowed me to remove the .active class from all the links, but ideally I want it to work when window.scrollY < 450 which doesn't seem to work.
My JavaScript is as follows:
const highlightNav = () => {
const links = document.querySelectorAll('.link');
const section = document.querySelectorAll('.section');
//updated if statement
//I tried using window.scrollY < 450 in the if statement below with no success
if (window.scrollY === 0) {
links.forEach((link) => link.classList.remove('active'));
};
if (window.scrollY !== 0) {
const changeLinkState = () => {
let index = section.length;
while(--index && window.scrollY + 450 < section[index].offsetTop) {};
links.forEach((link) => link.classList.remove('active'));
links[index].classList.add('active');
};
changeLinkState();
};
};
highlightNav();
window.addEventListener('scroll', highlightNav);
The HTML follows this structure:
<header>
<div class="nav-bar">
<nav>
About
Pricing
Contact
Sign in
</nav>
</div>
<section>
<!-- Landing Screen -->
</section>
</header>
<article>
<section id="about" class="section">
<!-- section 1 -->
</section>
<section id="price" class="section">
<!-- Section two -->
</section>
<section id="contact" class="section">
<!-- Section Three -->
</section>
</article>
css:
.nav-bar {
position: fixed;
top: 0px;
width: 100%;
height: 8vh;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #297094;
text-align: center;
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.3);
}
nav {
width: 25%;
height: 100%;
display: flex;
justify-content: flex-end;
align-items: center;
}
nav a {
padding: 0 10%;
border-left: 1px solid black;
height: 100%;
display: flex;
align-items: center;
font-size: 1.25rem;
color: white;
transition: background-color 600ms;
text-decoration: none;
white-space: nowrap;
}
nav a:hover {
background-color: #3B80A0;
cursor: pointer;
}
.active {
background-color: #3B80A0;
}
I'm very new to JavaScript so any help is welcome, thanks in advance.
Main issue:
Instead of mixing that 450 math into a while loop, define that amount as a constant with a descriptive name, then use it in your conditional.
A couple other notes:
The while loop in your posted code doesn't do anything, since there's nothing inside the {}
You call links.forEach((link) => link.classList.remove('active')); twice, so make that its own function (I've named it clearAllActive() here).
const highlightNav = () => {
const links = document.querySelectorAll('.link');
const sections = document.querySelectorAll('.section');
const clearAllActive = () => links.forEach(link => link.classList.remove('active'));
const highlightActive = () => {
// your highlighting code here
};
// don't start highlighting until we've scrolled at least this far
const minScrollBeforeHighlighting = 450;
if (window.scrollY < minScrollBeforeHighlighting) {
clearAllActive();
} else {
highlightActive();
};
};
highlightNav();
window.addEventListener('scroll', highlightNav);
I'm creating a DropDown List box and each item in the list has a remove (X) button to remove the item from the list. How is it possible to show the remove button "only" when the item is hovered over?
The current code shows the clear button each each item but I only want it to show when the item is hovered over
Sorry, here is the code
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
const ListWrapper = styled.div`
position: absolute;
width: 16rem;
z-index: 1;
background: white;
&:hover {
cursor: pointer;
}
`;
const ListMenu = styled.div`
position: absolute;
width: 100%;
z-index: 1;
background: white;
overflow-x: hidden;
`;
const ListMenuHeader = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-end;
`;
const DropdownText = Text.Link.extend`
padding-top: 3rem;
`;
const DropdownButton = styled.div`
padding: 1 rem 0.75rem;
`;
const ListMenuItem = styled.div`
display: flex;
background-color: grey)};
color: grey};
>[name~=icon] {
right: 0rem;
border-radius: 0;
background: none;
align-items: right;
justify-content: right;
&:hover {
background-color: grey)};
}
&:focus {
outline: none;
}
`;
class ListListMenu extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
text: PropTypes.node.isRequired,
items: PropTypes.arrayOf(PropTypes.any).isRequired,
component: PropTypes.func.isRequired,
selectedItem: PropTypes.any,
getItemProps: PropTypes.func.isRequired,
highlightedIndex: PropTypes.number,
closeListMenu: PropTypes.func.isRequired,
};
static defaultProps = {
selectedItem: null,
highlightedIndex: null,
}
onClearClick = (items,item1) => (item) => {
const index = items.indexOf(item1);
if (index > -1) {
items.splice(index, 1);
}
}
render() {
const {
id, text, items, component, selectedItem, getItemProps,
highlightedIndex, closeListMenu,
} = this.props;
return (
<ListWrapper id={id} >
<ListMenuHeader onClick={closeListMenu}>
<DropdownText>{text}</DropdownText>
<DropdownButton
id={`${id}-button`}
>
<Icon type="caret-up" appearance="neutral" />
</DropdownButton>
</ListMenuHeader>
<ListMenu>
{selectedItems.map((item, index) => (
<ListMenuItem
{...getItemProps({
item,
isActive: highlightedIndex === index,
isSelected: _.isEqual(selectedItem, item),
})}
key={index}
>
{React.createElement(component, { item })}
<Button // CLEAR BUTTON
name={item}
id={item}
icon="remove"
onClick={this.onClearClick(items, item)}
circle
display="flat"
appearance="disabled"
id="clear-search-button"
/>
</ListMenuItem>
))}
</ListMenu>
</ListWrapper>
);
}
}
export default ListListMenu;
Here is one way you could probably just have that "x" appear on hover.
Instead of looking for a "hover" event, what about looking for an "onmouseenter" event combined with "onmouseleave"?
Like so...
class Example extends React.Component {
onHover() {
this.refs.deleteX.style.display = "block";
}
onExit() {
this.refs.deleteX.style.display = "none";
}
render() {
return (
<div>
<input onmouseenter={ this.onHover } onmouseleave={ this.onExit } />
<p ref="deleteX">x</p>
</div>
)
}
}
Kind of like this post
I found some relevant questions with this but none of them can't solve my problem. So, I wrote this post. If there are any relevant threads, please let me know.
I am trying to get the size (in px unit) of div element so that I can draw some SVG groups inside of it. For that, I wrote following React class after searching the web for a while.
class ChartBox extends React.Component {
constructor() {
super();
this.state = {width: 0, height: 0}
}
componentDidMount() {
window.addEventListener('resize', () => this.handleResize());
this.handleResize();
}
componentWillUnmount() {
window.removeEventListener('resize', () => this.handleResize());
}
handleResize = () => {
this.setState({
width: this.container.offsetWidth,
height: this.container.offsetHeight
});
}
render() {
return (
<div className={this.props.className}>
<div className={theme.up}>
<div className={theme.left}>up.left</div>
<div className={theme.right}
ref={c => this.container = c}>
<p>up.right</p>
<p>`${this.state.width}x${this.state.height}`</p>
</div>
</div>
<div className={theme.down}>
<div className={theme.left}> down.left </div>
<div className={theme.right}>down.right</div>
</div>
</div>
);
}
}
The ChartBox class get a style of the outer-most div element from a parent React element. And for all inner div elements in the ChartBox class, I import following css.
:root {
--right-width: 100px;
--top-height: 100px;
--left-width: calc(100% - var(--right-width));
--bottom-height: calc(100% - var(--top-height));
}
.up {
float: left;
width: 100%;
height: var(--top-height);
padding: 0px
}
.bottom {
float: left;
width: 100%;
height: var(--bottom-height);
padding: 0px
}
.left {
float: left;
width: var(--left-width);
height: 100%;
padding: 0px
}
.right {
float: left;
width: var(--right-width);
height: 100%;
padding: 0px
}
As you can imagine, I am trying to divide the outer-most div element into four sections where the smallest div element has a size of 100px by 100px.
First of all, all elements are mounted correctly when I checked it visually. However, the returned values are incorrect. For example, when I first reload the page, it returns 762 x 18 that is incorrect. But after resizing the window, it returns correct size as 100 x 100.
Any suggestions or comments to solve this issue?
I had a similar issue. I had to use setTimeout(0) for this. For example:
handleResize = () => {
setTimeout(() => {
this.setState({
width: this.container.offsetWidth,
height: this.container.offsetHeight
});
}, 0 );
}
This ensures that the function call happens at the end of the call stack and after the container has fully rendered.
Update:
I actually found a better solution using getSnapshotBeforeUpdate. No set timeout is needed with this. From the React docs: "It enables your component to capture some information from the DOM (e.g. scroll position) before it is potentially changed."
For example:
getSnapshotBeforeUpdate() {
let width = this.container.offsetWidth;
let height = this.container.offsetHeight;
return { width, height };
}
componentDidUpdate( prevProps, prevState, snapshot ) {
if (prevState.width !== snapshot.width || prevState.height !==
snapshot.height) {
this.setState({
height: snapshot.height,
width: snapshot.width,
})
}
}