resizing divs on click - javascript

I've been trying to add a onClick event to the divs which will resize a div to fullscreen when clicking on that div but when I click on a div, all the div are getting expanded. How do I restrict that onClick event to only a single a div and make that single div resize to full screen? I've also added transition so that when it resize to fullscreen, it looks like a animation but all the divs have been affected by it when clicking on just a single div
import React from "react";
import "./styles.scss";
const colors = [
"palevioletred",
"red",
"green",
"blue",
"yellow",
"orange",
"lightblue"
];
const randomColor = items => {
return items[randomHeight(0, items.length)];
};
const randomHeight = (min, max) => {
return Math.floor(min + Math.random() * (max - min + 1));
};
export default class App extends React.Component {
constructor(props) {
super(props);
this.addActiveClass = this.addActiveClass.bind(this);
this.state = {
active: false
};
}
addActiveClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
return (
<div class="container">
{Array.from({ length: 30 }).map((item, index) => (
<div
key={index}
style={{
background: randomColor(colors),
height: randomHeight(100, 200)
}}
className={this.state.active ? "full" : null}
onClick={this.addActiveClass}
/>
))}
</div>
);
}
}
* {
box-sizing: border-box;
}
body {
margin: 40px;
background-color: #fff;
color: #444;
font: 2em Sansita, sans-serif;
}
.container {
display: flex;
flex-direction: column;
flex-wrap: wrap;
max-height: 100vh;
}
.container > * {
border: 2px solid orange;
border-radius: 5px;
margin: 10px;
padding: 10px 20px;
background-color: red;
color: #fff;
}
.full{
width: 100%;
height: 100%;
transition: 2s;
}
codesandbox

Currently you're sharing one state with all of the divs. In order to resolve this problem, create activeIndex state, initialize it with -1 maybe, and use it like:
// ...
class App extends React.Component {
constructor(props) {
super(props);
this.addActiveClass = this.addActiveClass.bind(this);
this.state = {
activeIndex: -1
};
}
addActiveClass(activeIndex) {
this.setState(prev => ({
activeIndex: prev.activeIndex === activeIndex ? -1 : activeIndex
}));
}
render() {
return (
<div className="container">
{Array.from({ length: 30 }).map((item, index) => {
return (
<div
key={index}
style={{
background: randomColor(colors),
height: randomHeight(100, 200)
}}
className={this.state.activeIndex === index ? "full" : ""}
onClick={() => this.addActiveClass(index)}
/>
);
})}
</div>
);
}
}

Related

How can I implement conditional rendering using map function?

I made 5 blocks and want to make the letters on each block thick when the mouse is hover. I made isHover state and changed the thickness of the writing according to the state, but the problem is that the thickness of all five changes. I think I can solve it by using conditional rendering, but I don't know how to use it. Of course, it can be implemented only with css, but I want to implement it with conditional rendering because I am practicing the code concisely.
import "./styles.css";
import styled from "styled-components";
import { useState } from "react";
export default function App() {
const array = [
{ id: "1", title: "ABC" },
{ id: "2", title: "DEF" },
{ id: "3", title: "GHI" },
{ id: "4", title: "JKL" },
{ id: "5", title: "MNO" }
];
const [isHover, setIsHover] = useState(false);
return (
<Head isHover={isHover}>
<div className="header">
{array.map((content, id) => {
return (
<div
className="header__title"
onMouseEnter={() => {
setIsHover(true);
}}
onMouseLeave={() => {
setIsHover(false);
}}
>
{content.title}
</div>
);
})}
</div>
</Head>
);
}
const Head = styled.div`
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
.header {
display: inline-flex;
border: 1px solid black;
box-sizing: border-box;
}
.header__title {
border: 1px solid red;
padding: 5px 10px;
font-weight: ${(props) => (props.isHover ? "700" : "400")};
}
`;
codesandbox
https://codesandbox.io/s/aged-cherry-53pr2r?file=/src/App.js:0-1170
The problem is that you are using the same state for all the 5 blocks. There are multiple approaches you could take to solve this problem.
1. Multiple states
You could create 5 different isHover<N> states (maybe a single one, but as an array)
2. Component extraction
You could just extract out a component for each entry in array and do state management in that component.
function App() {
const array = [...];
return (
<Head>
<div className="header">
{array.map((content, id) => (
<HeaderTitle key={content.id} content={content} />
)}
</div>
</Head>
);
}
function HeaderTitle({ content }) {
const [isHover, setIsHover] = useState(false);
return (
<StyledHeaderTitle
isHover={isHover}
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
>
{content.title}
</StyledHeaderTitle>
);
}
const StyledHeaderTitle = styled.div`
font-weight: ${(props) => (props.isHover ? "700" : "400")};
`
3. Using style prop
Directly apply the font weight using the style prop (An extension to approach 2)
function HeaderTitle({ content }) {
const [isHover, setIsHover] = useState(false);
return (
<StyledHeaderTitle
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
style={{ fontWeight: isHover ? "700" : "400" }}
>
{content.title}
</StyledHeaderTitle>
);
}
4. CSS
CSS already allows you to track hover states over different elements and you don't need to manually track it in javascript.
.header__title {
border: 1px solid red;
padding: 5px 10px;
font-weight: 400;
&:hover {
font-weight: 700;
}
}
There's no need to use React state and event listeners here, you can do it all in CSS instead:
.header__title {
border: 1px solid red;
padding: 5px 10px;
font-weight: 400;
}
.header__title:hover {
font-weight: 700;
}
Just add this pseudo class and you're good to go
.header__title:hover {
font-weight: 700;
}

JavaScript(React)- Uncaught TypeError: Cannot read properties of undefined (reading 'style')

I am working in a project using React Js. When I wrote this, Syntax: movieRef.current.style.transform = translateX(210px); it's not working. It shows an error- can't read 'style'. How can I solve this problem. I have attached my codes here.
import React, { useState, useRef } from "react";
import { useGetMoviesQuery } from "../../services/movieApi";
import SingleMovie from "../SingleMovie/SingleMovie";
import "./MovieRow.scss";
import "../SingleMovie/SingleMovie.scss";
import { MdArrowForwardIos, MdArrowBackIos } from "react-icons/md";
// const MovieRow = ({title}) => {codes}
const MovieRow = (props) => {
const { title, fetchURI } = props;
const { data, isLoading } = useGetMoviesQuery(fetchURI);
const movieRef = useRef();
// console.log("data:", data);
if (isLoading) {
return (
<div>
<h1 style={{ textAlign: "center", marginTop: "250px" }}>Loading...</h1>
</div>
);
}
const handleClick = (direction) => {
if (direction === "left") {
movieRef.current.style.transform = `translateX(210px)`;
}
};
return (
<>
<div className="movie-row-container">
<h1>{title}</h1>
<div className="wrapper">
<MdArrowForwardIos
className="slider right-arrow"
onClick={() => handleClick("right")}
/>
<div className="movie-row-block" ref={movieRef}>
{data?.results.map((movie, index) => (
<SingleMovie key={movie?.id} singleMovie={movie} index={index} />
))}
</div>
<MdArrowBackIos
className="slider left-arrow"
onClick={handleClick("left")}
/>
</div>
</div>
</>
);
};
export default MovieRow;
//MovieRow.scss file
h1 {
font-family: sans-serif;
font-size: 100%;
color: white;
}
// non-global css
.movie-row-container {
margin-top: 25px;
margin-left: 22px;
.wrapper {
position: relative;
.slider {
height: 100%;
width: 70px;
background-color: rgba(12, 42, 214, 0.438);
color: white;
position: absolute;
z-index: 99;
margin: 0 auto;
cursor: pointer;
&.right-arrow {
right: 0;
}
&.left-arrow {
left: 0;
}
}
.movie-row-block {
display: flex;
overflow-x: scroll;
overflow-y: hidden;
transform: translateX(210px);
}
}
.movie-row-block::-webkit-scrollbar {
display: none;
}
}
This part is firing infinitely even before the first render, that's why you'll get undefined on style:
<MdArrowBackIos
className="slider left-arrow"
onClick={handleClick("left")}
/>
Change it to this:
<MdArrowBackIos
className="slider left-arrow"
onClick={handleClick.bind(undefined, "left")}
/>

How to achieve a clickable icon using an OnClick event in React that holds a setTimeout but also a clearTimeout?

THE PHOTO BELOW SHOWS WHAT I WANT TO ACHIEVE. Basically I have a component where when I hover some arrows (up and down) appears but when the user click those arrows the background color changes, but just on the click itself. and the background color does not remain clicked. I tried to achieve that with a setTimeout on the click event. I can let the timer work on the click but the clearTimeout is not working. Any clues? the code is also below (after the photo).
THIS IS MY CODE:
//rafce
import React, { useState } from 'react';
// styled components
import styled from 'styled-components';
// icons
import { IoIosArrowUp, IoIosArrowDown } from 'react-icons/io';
const DurationIntervalComponent = () => {
const [hours, setHours] = useState('0');
const [showHoursArrows, setShowHourArrows] = useState(false);
const [arrowActiveUp, setArrowActiveUp] = useState(false);
const [arrowActiveDown, setArrowActiveDown] = useState(false);
const incrementHours = (value) => {
let timer = setTimeout(() => setArrowActiveUp(true), 500);
clearTimeout(timer, 1000)
setHours((prevHours) => {
// if there is nothing
if (!prevHours) {
return '0';
} else if (+prevHours >= 24) {
return '0';
} else {
return String(+prevHours + 1);
}
});
};
const decrementHours = (value) => {
setArrowActiveDown(true);
setHours((prevHours) => {
// if there is nothing
if (!prevHours) {
return '0';
} else if (+prevHours <= 0) {
return '24';
} else {
return String(+prevHours - 1);
}
});
};
return (
<Container>
<Row>
<p> Interval* </p>
<Inputs>
<Selection
onMouseEnter={() => setShowHourArrows(true)}
onMouseLeave={() => setShowHourArrows(false)}
>
{showHoursArrows && (
<p
className="icon"
onClick={incrementHours}
style={
arrowActiveUp
? { backgroundColor: 'red' }
: { backgroundColor: 'none' }
}
>
<IoIosArrowUp />
</p>
)}
<SquareInput value={hours} />
<p>hours</p>
{showHoursArrows && (
<p className="icon" onClick={decrementHours}>
<IoIosArrowDown />
</p>
)}
</Selection>
<div>
<SquareInput />
<p>minutes</p>
</div>
</Inputs>
</Row>
<hr />
<Row>
<p> Duration* </p>
<Inputs>
<SquareInput />
<p>:</p>
<SquareInput />
<p>to</p>
<SquareInput />
<p>:</p>
<SquareInput />
</Inputs>
</Row>
</Container>
);
};
const Container = styled.div`
border: 1px solid #c5c5c5;
border-radius: 15px;
`;
const Row = styled.div`
display: flex;
align-items: center;
height: 150px;
justify-content: space-between;
padding: 20px;
`;
const Inputs = styled.div`
display: flex;
align-items: center;
p {
margin-right: 5px;
font-size: 0.8em;
text-align: center;
}
`;
const Selection = styled.div`
.icon {
font-size: 1.2em;
padding: 0;
}
`;
const SquareInput = styled.input`
width: 50px;
height: 50px;
border: 1px solid #c5c5c5;
border-radius: 10px;
outline: none;
margin-right: 5px;
font-size: 1.5em;
text-align: center;
`;
export default DurationIntervalComponent;

ReactJS - pass object keys and values as props to div

In my Class component Field.jsx render(), I'm expanding my <Position> component using <Flipper>, (an abstracted flip animation), like so:
import { Flipper, Flipped } from 'react-flip-toolkit'
import { Position } from "./Position";
import "./css/Position.css";
class Field extends Component {
constructor(props) {
super(props);
this.state = {
fullScreen: false,
};
}
toggleFullScreen() {
this.setState({ fullScreen: !this.state.fullScreen });
}
...
render() {
const { players } = this.props;
const { fullScreen } = this.state;
if(players){
return (
<div className="back">
<div className="field-wrapper" >
<Output output={this.props.strategy} />
<Flipper flipKey={fullScreen}>
<Flipped flipId="player">
<div className="field-row">
{this.getPlayersByPosition(players, 5).map((player,i) => (
<Position
key={i}
className={fullScreen ? "full-screen-player" : "player"}
getPositionData={this.getPositionData}
toggleFullScreen={this.toggleFullScreen.bind(this)}
>{player.name}</Position>
))}
</div>
</Flipped>
</Flipper>
</div>
</div>
);
}else{
return null}
}
When I render it, I get clickable items from the mapped function getPlayersByPosition(), like so:
And if I click on each item, it expands to a div with player name:
Which is passed as props.children at component <div>
Position.jsx
import React from "react";
import "./css/Position.css";
export const Position = props => (
<div
className={props.className}
onClick={() => {
props.getPositionData(props.children);
props.toggleFullScreen();
console.log(props.getPositionData(props.children))
}}
>
{props.children}
</div>
);
getPositionData(), however, returns an object with many items on its turn, as seen by console above:
{matches: 7, mean: 6.15, price: 9.46, value: 0.67, G: 3, …}
QUESTION:
How do I pass and print theses other props keys and values on the expanded purple div as text?, so as to end with:
Patrick de Paula
matches: 7
mean: 6.15
price:9.46
....
NOTE:
Position.css
.position-wrapper {
height: 4em;
display: flex;
justify-content: center;
align-items: center;
font-weight: lighter;
font-size: 1.4em;
color: #888888;
flex: 1;
/*outline: 1px solid #888888;*/
}
.player {
height: 4em;
width: 4em;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
font-weight: lighter;
font-size: 1.4em;
/*background-color: #66CD00;*/
color: #ffffff;
}
.full-screen-player {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
cursor: pointer;
background-image: linear-gradient(
45deg,
rgb(121, 113, 234),
rgb(97, 71, 182)
);
}
Looks like the props are all set & ready to be print as seen on your console. You can access them via props.getPositionData(props.children).property_name_here or destructure them
export const Position = props => {
const { matches, mean, price } = props.getPositionData(props.children);
return (
<div
className={props.className}
onClick={() => {
props.getPositionData(props.children);
props.toggleFullScreen();
console.log(props.getPositionData(props.children))
}}
>
<p>Name: {props.children}</p>
<p>Matches: {matches}</p>
<p>Mean: {mean}</p>
<p>Price: {price}</p>
</div>
)
}
Regarding the issue on the fullScreen prop (see comments section):
Is there a way to print them ONLY after toggleFullScreen()
Since you already have a state on the Field component which holds your fullScreen value, on your Field component, you need to pass the fullScreen prop as well to the Position component. e.g., fullScreen={this.state.fullScreen}. Back on Position component, have some condition statements when you are rendering.
Example:
<>
{props.fullScreen &&
<p>Name: {props.children}</p>
}
</>

React custom dropdown with event listener

I created a Dropdown that when I click outside of it the dropdown disappears. I used a click event listener to determine if I clicked outside the dropdown.
After a few clicks, the page slows down and crashes. Perhaps the state is being rendered in a loop or too many events are being fired at once?
How do I fix this?
Also, is there a more React way to determine if I clicked outside an element? (Instead of using a document.body event listener)
Here is the codepen:
const items = [
{
value: 'User1'
},
{
value: 'User2'
},
{
value: 'User3'
},
{
value: 'User4'
},
{
value: 'User5'
}
];
class Dropdown extends React.Component {
state = {
isActive: false,
}
render() {
const { isActive } = this.state;
document.addEventListener('click', (evt) => {
if (evt.target.closest('#dropdownContent')) {
//console.warn('clicked inside target do nothing');
return;
}
if (evt.target.closest('#dropdownHeader')) {
//console.warn('clicked the header toggle');
this.setState({isActive: !isActive});
}
//console.warn('clicked outside target');
if (isActive) {
this.setState({isActive: false});
}
});
return (
<div id="container">
<div id="dropdownHeader">select option</div>
{isActive && (
<div id="dropdownContent">
{items.map((item) => (
<div id="item" key={item.value}>
{item.value}
</div>
))}
</div>
)}
</div>
);
};
}
ReactDOM.render(
<Dropdown items={items} />,
document.getElementById('root')
);
#container {
position: relative;
height: 250px;
border: 1px solid black;
}
#dropdownHeader {
width: 100%;
max-width: 12em;
padding: 0.2em 0 0.2em 0.2em;
margin: 1em;
cursor: pointer;
box-shadow: 0 1px 4px 3px rgba(34, 36, 38, 0.15);
}
#dropdownContent {
display: flex;
flex-direction: column;
position: absolute;
top: 3em;
width: 100%;
max-width: 12em;
margin-left: 1em;
box-shadow: 0 1px 4px 0 rgba(34, 36, 38, 0.15);
padding: 0.2em;
}
#item {
font-size: 12px;
font-weight: 500;
padding: 0.75em 1em 0.75em 2em;
cursor: pointer;
}
<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>
<div id="root">
<!-- This element's contents will be replaced with your component. -->
</div>
There's a pretty simple explanation for what you're experiencing. :)
The way I was able to figure it out was the number of warnings that were showing up in the terminal every time I clicked somewhere was getting higher and higher, especially when the state changed.
The answer though is that since you were adding the event listener code in the render function, every time the code re-rendered it would add more and more event listeners slowing down your code.
Basically the solution is that you should move the adding of event listeners to componentDidMount so it's only run once.
Updated working javascript:
const items = [
{
value: 'User1'
},
{
value: 'User2'
},
{
value: 'User3'
},
{
value: 'User4'
},
{
value: 'User5'
}
];
class Dropdown extends React.Component {
state = {
isActive: false,
}
// added component did mount here
componentDidMount(){
const { isActive } = this.state;
document.addEventListener('click', (evt) => {
if (evt.target.closest('#dropdownContent')) {
console.warn('clicked inside target do nothing');
return;
}
if (evt.target.closest('#dropdownHeader')) {
console.warn('clicked the header toggle');
this.setState({isActive: !isActive});
}
console.warn('clicked outside target');
if (isActive) {
this.setState({isActive: false});
}
});
}
render() {
const { isActive } = this.state;
//removed event listener here
return (
<div id="container">
<div id="dropdownHeader">select option</div>
{isActive && (
<div id="dropdownContent">
{items.map((item) => (
<div id="item" key={item.value}>
{item.value}
</div>
))}
</div>
)}
</div>
);
};
}
ReactDOM.render(
<Dropdown items={items} />,
document.getElementById('root')
);

Categories

Resources