Related
I am making a basic drop down menu in React. I am new to React and have managed to get to where the menu opens on click and such. What I would like to do is change the background of each choice to a different color only when the mouse is hovered over that specific element.
Right now the issue is that every element is highlighted when one is hovered over. I understand this is because I am changing the state which effects every element, but I cant wrap my mind around how to implement it how I would like it. Perhaps there is a React concept(s) I am unaware of that would make this easier.
Here is my code so far:
App.jsx file
import React from "react";
import InnerDiv from './components/InnerDiv';
import CountiesList from "./components/countiesList";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
menuIsOpen: false,
height: '50px'
}
this.handleClick = this.handleClick.bind(this)
}
handleClick = () => {
if (this.state.menuIsOpen == false) {
this.setState(state => ({
menuIsOpen: !state.menuIsOpen,
height: '350px'
}))
} else {
this.setState(state => ({
menuIsOpen: !state.menuIsOpen,
height: '50px'
}))
}
}
render() {
const divStyle = {
height: this.state.height,
width: '300px',
border: '2px solid cornflowerblue',
borderRadius: '12px',
position: 'absolute',
top: '50px',
left: '50px',
cursor: 'pointer',
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'center',
transition: 'all 0.1s linear',
}
return (
<div onClick={this.handleClick} style={divStyle} className="App">
<InnerDiv/>
<CountiesList/>
</div>
);
}
}
export default App;
InnerDiv.jsx file
import React from "react";
const innerDivStyle = {
height: '50px',
width: '90%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between'
}
const h1Style = {
fontSize: '20px',
WebkitTouchCallout: 'none',
WebkitUserSelect: 'none',
khtmlUserSelect: 'none',
MozUserSelect: 'none',
msUserSelect: 'none',
UserSelect: 'none'
}
const svgStyle = {
height: '30px'
}
class InnerDiv extends React.Component {
constructor(props) {
super(props);
}
render(){
return (
<div style={innerDivStyle} className="InnerDiv">
<h1 style={h1Style}>Select A Location</h1>
<svg style={svgStyle} xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 122.88 122.88"><title>round-line-bottom-arrow</title><path d="M122.85,61.45h0A61.39,61.39,0,0,0,61.45,0V0h0V0A61.38,61.38,0,0,0,0,61.43H0v0H0a61.35,61.35,0,0,0,61.4,61.38v0h0v0a61.34,61.34,0,0,0,61.38-61.4ZM61.44,91,33.92,60.47H51.5V39.2H71.38V60.47H89L61.44,91Zm48.28-29.54h0a48.36,48.36,0,0,1-48.27,48.29v0h0v0A48.35,48.35,0,0,1,13.14,61.47h0v0h0A48.27,48.27,0,0,1,61.41,13.14v0h0v0a48.3,48.3,0,0,1,48.27,48.3Z"/></svg>
</div>
)
}
}
export default InnerDiv;
countiesList.jsx file
import React from "react";
class CountiesList extends React.Component {
constructor(props) {
super(props);
this.state = {
counties: [
'Antrim',
'Armagh',
'Carlow',
'Cavan',
'Clare',
'Cork',
'Derry',
'Donegal',
'Down',
'Dublin',
'Fermanagh',
'Galway',
'Kerry',
'Kildare',
'Kilkenny',
'Laois',
'Leitrim',
'Limerick',
'Longford',
'Louth',
'Mayo',
'Meath',
'Monaghan',
'Offaly',
'Roscommon',
'Sligo',
'Tipperary',
'Tyrone',
'Waterford',
'Westmeath',
'Wexford',
'Wicklow'
],
backgroundColor: '#a9c4f5'
};
this.updateBackgroundColor = this.updateBackgroundColor.bind(this);
this.reverseBackgroundColor = this.reverseBackgroundColor.bind(this);
}
updateBackgroundColor() {
this.setState({backgroundColor: 'cornflowerblue'})
}
reverseBackgroundColor() {
this.setState({backgroundColor: '#a9c4f5'})
}
render() {
const ulStyle = {
listStyleType: 'none',
paddingInlineStart: 0,
margin: 0,
width: '100%',
height: '300px',
overflowY: 'scroll',
borderBottomLeftRadius: '12px'
}
const liItemContainer = {
height: '50px',
paddingLeft: '15px',
display: 'flex',
alignItems: 'center',
background: this.state.backgroundColor,
}
const liStyle = {
fontWeight: '700'
}
let countiesListItems = this.state.counties.map(county => {
return (
<div key={county} style={liItemContainer} onMouseEnter={this.updateBackgroundColor} onMouseOut={this.reverseBackgroundColor}>
<li style={liStyle}>{county}</li>
</div>
)
})
return (
<ul style={ulStyle}>
{countiesListItems}
</ul>
)
}
}
export default CountiesList;
Thank you for your help in advance
I am new to React. My issue is that I am trying to change the background color of a div that I dynamically render in React. To do this I learned about useRef() to single out the element.
If I do a console.log() in the onHover function, the log works in the browser, but the background color of the element does not change for some reason, even though I have implemented useRef how others have for this exact same reason.
Thank you in advance for your help, here is the code:
import React, { useRef } from "react";
const CountiesList = () => {
const counties = [
'Antrim',
'Armagh',
'Carlow',
'Cavan',
'Clare',
'Cork',
'Derry',
'Donegal',
'Down',
'Dublin',
'Fermanagh',
'Galway',
'Kerry',
'Kildare',
'Kilkenny',
'Laois',
'Leitrim',
'Limerick',
'Longford',
'Louth',
'Mayo',
'Meath',
'Monaghan',
'Offaly',
'Roscommon',
'Sligo',
'Tipperary',
'Tyrone',
'Waterford',
'Westmeath',
'Wexford',
'Wicklow'
]
const liItem = useRef(null)
const onHover = () => {
liItem.current.style.backgroundColor = 'cornflowerblue'
}
const ulStyle = {
listStyleType: 'none',
paddingInlineStart: 0,
margin: 0,
width: '100%',
height: '300px',
overflowY: 'scroll',
borderBottomLeftRadius: '12px'
}
const liItemContainer = {
height: '50px',
paddingLeft: '15px',
display: 'flex',
alignItems: 'center',
backgroundColor: '#a9c4f5'
}
const liStyle = {
fontWeight: '700'
}
let countiesListItems = counties.map(county => {
return (
<div ref={liItem} key={county} style={liItemContainer} onMouseOver={onHover}>
<li style={liStyle}>{county}</li>
</div>
)
})
return (
<ul style={ulStyle}>
{countiesListItems}
</ul>
)
}
export default CountiesList;
That's because you use a single ref on an array of elements, You need to use an array of template refs or just pass the target element by onMouseOver={(e) => onHover(e.target)} and change the style directly without the need to template refs
import React, { useRef } from "react";
const CountiesList = () => {
const counties = [
'Antrim',
'Armagh',
'Carlow',
'Cavan',
'Clare',
'Cork',
'Derry',
'Donegal',
'Down',
'Dublin',
'Fermanagh',
'Galway',
'Kerry',
'Kildare',
'Kilkenny',
'Laois',
'Leitrim',
'Limerick',
'Longford',
'Louth',
'Mayo',
'Meath',
'Monaghan',
'Offaly',
'Roscommon',
'Sligo',
'Tipperary',
'Tyrone',
'Waterford',
'Westmeath',
'Wexford',
'Wicklow'
]
const liItem = useRef(null)
const onHover = (element) => {
element.style.backgroundColor = 'cornflowerblue'
}
const ulStyle = {
listStyleType: 'none',
paddingInlineStart: 0,
margin: 0,
width: '100%',
height: '300px',
overflowY: 'scroll',
borderBottomLeftRadius: '12px'
}
const liItemContainer = {
height: '50px',
paddingLeft: '15px',
display: 'flex',
alignItems: 'center',
backgroundColor: '#a9c4f5'
}
const liStyle = {
fontWeight: '700'
}
let countiesListItems = counties.map(county => {
return (
<div ref={liItem} key={county} style={liItemContainer} onMouseOver={(e) => onHover(e.target)}>
<li style={liStyle}>{county}</li>
</div>
)
})
return (
<ul style={ulStyle}>
{countiesListItems}
</ul>
)
}
export default CountiesList;
If you really want to do your hover effect "in software" (i.e. not with CSS, which is much more preferred for a purely visual effect), then the React way is to use state, not refs.
const ulStyle = {
listStyleType: "none",
paddingInlineStart: 0,
margin: 0,
width: "100%",
height: "300px",
overflowY: "scroll",
borderBottomLeftRadius: "12px"
};
const liItemContainer = {
height: "50px",
paddingLeft: "15px",
display: "flex",
alignItems: "center"
};
const liStyle = {
fontWeight: "700"
};
const counties = [
"Antrim",
"Armagh",
"Carlow",
"Cavan",
// ...
];
const CountiesList = () => {
const [hoveredCounty, setHoveredCounty] = React.useState(null);
const onHover = (event) => {
setHoveredCounty(event.currentTarget.dataset.county);
};
return (
<ul style={ulStyle}>
{counties.map((county) => (
<div
data-county={county}
key={county}
style={{
...liItemContainer,
background: hoveredCounty === county ? "orange" : "#a9c4f5"
}}
onMouseOver={onHover}
>
<li style={liStyle}>{county}</li>
</div>
))}
</ul>
);
};
Currently I have the following layout in in react web page:
The map is an embedded component from an external site. The cards are filtered from a list and it renders the matching items. What I want to do is to make the cards appear on the right of the map instead of below. I've tried with a variety of css and div tricks but can't seem to make it work. Any suggestions are welcome.
Code:
import React, { useState, useEffect } from "react";
import axios from "axios";
import { Input } from "semantic-ui-react";
import BasicCard from "../components/BasicCard";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
root: {
display: "fixed",
marginTop: "15vh",
paddingLeft: "5vh",
width: "50vh",
},
map: {
marginTop: "5vh",
display: "block",
justifyContent: "left",
alignContent: "left",
},
cardHolder: {
display: "inline",
paddingLeft: "50vh",
justifyContent: "right",
justifyItems: "right",
alignContent: "right",
alignItems: "right",
},
searchBox: {
display: "block",
height: 1,
justifyContent: "left",
justifyItems: "left",
alignContent: "left",
alignItems: "left",
},
}));
export default function Posts() {
const [APIData, setAPIData] = useState([]);
const [filteredResults, setFilteredResults] = useState([]);
const [searchInput, setSearchInput] = useState("");
const classes = useStyles();
useEffect(() => {
const delayDebounceFn = setTimeout(() => {
console.log(searchInput);
searchItems(searchInput);
}, 1000);
axios
.get(
`https://data.gov.sg/api/action/datastore_search?resource_id=cdab7435-7bf0-4fa4-a8bd-6cfd231ca73a&limit=1000`
)
.then((response) => {
setAPIData(response.data.result.records);
console.log(response.data.result.records);
});
return () => clearTimeout(delayDebounceFn);
}, [searchInput]);
const sleep = (time) => {
return new Promise((resolve) => setTimeout(resolve, time));
};
const searchItems = (searchValue) => {
setSearchInput(searchValue);
if (searchInput !== "") {
const filteredData = APIData.filter((item) => {
console.log(Object.values(item).join("").toLowerCase());
return Object.values(item)
.join("")
.toLowerCase()
.includes(searchInput.toLowerCase());
});
setFilteredResults(filteredData);
} else {
setFilteredResults(APIData);
}
};
return (
<div className={classes.root}>
<Input
icon='search'
placeholder='Search...'
onChange={(e) => setSearchInput(e.target.value)}
className={classes.searchBox}
/>
<iframe
className={classes.map}
width='700'
height='400'
src='https://data.gov.sg/dataset/list-of-verified-public-access-aed-locations/resource/b0f395af-d297-4c22-8523-23fff99b17a7/view/3ea52d19-937b-4b2a-9da0-7b74e1d72005'
frameBorder='10'
>
{" "}
</iframe>
{searchInput.length > 1
? filteredResults.map((item) => {
return (
<BasicCard
variant='outlined'
raised={true}
sx={{
border: 1,
gap: 1,
borderColor: "text.primary",
alignItems: "right",
alignContent: "right",
}}
roadName={item.road_name}
postalCode={item.postal_code}
locationDesc={item.aed_location_description}
floorLevel={item.aed_location_description}
operatingHours={item.aed_location_floor_level}
></BasicCard>
);
})
: APIData.map((item) => {
return;
aaaaa;
})}
</div>
);
}
I have const experience that creates 6 experiences with there popover. I am supposed to add useCallback to it but when I go I get and error.
This is my component experience
import React, { memo, useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '#material-ui/styles';
import Typography from '#material-ui/core/Typography';
import clsx from 'clsx';
import Button from '#material-ui/core/Button';
import Popover from '#material-ui/core/Popover';
import gastronomia from 'assets/experiences/gastronomia.jpg';
import productos from 'assets/experiences/productos.jpg';
import giftcard from 'assets/experiences/giftcard.jpg';
import diversion from 'assets/experiences/diversion.jpg';
import deporte from 'assets/experiences/deporte.jpg';
import belleza from 'assets/experiences/belleza.jpg';
import gastronomiaExperiences from 'data/gastronomia';
import productosExperiences from 'data/productos';
import giftcardExperiences from 'data/giftcard';
import diversionExperiences from 'data/diversion';
import deporteExperiences from 'data/deporte';
import bellezaExperiences from 'data/belleza';
// Proptypes definitions to the component.
const propTypes = {
/** Custom root className. */
className: PropTypes.string,
};
// Default props definitions.
const defaultProps = {
className: null,
};
// Component's styles
const useStyles = makeStyles(theme => ({
root: {
display: 'block',
margin: '0 auto',
maxWidth: '50%',
[theme.breakpoints.down('md')]: {
maxWidth: '70%',
},
[theme.breakpoints.down('sm')]: {
maxWidth: '100%',
},
'& .experiences-column': {
display: 'inline-block',
verticalAlign: 'top',
textAlign: 'center',
'&.col1': {
width: '36.31%',
[theme.breakpoints.down('sm')]: {
width: 'initial',
},
},
'&.col2': {
width: '63.69%',
[theme.breakpoints.down('sm')]: {
width: 'initial',
},
},
'& .experience': {
padding: 2,
position: 'relative',
'& img': {
width: '100%',
display: 'block',
},
'& .experience-title': {
position: 'absolute',
bottom: 30,
left: 0,
right: 0,
textAlign: 'center',
},
},
},
},
paper: {
width: '50%',
left: '25% !important',
height: '280px',
'& img': {
width: '100px',
padding: '0 10px 0 10px',
},
},
gastronomia: {
backgroundColor: 'rgba(0,185,208,0.9)',
},
giftcard: {
backgroundColor: 'rgba(221,165,174,0.9)',
},
deporte: {
backgroundColor: 'rgba(189,143,205,0.9)',
},
productos: {
backgroundColor: 'rgba(221,165,174,0.9)',
},
diversion: {
backgroundColor: 'rgba(255,176,10,0.9)',
},
belleza: {
backgroundColor: 'rgba(229,166,187,0.9)',
},
'#media screen and (max-width: 1024px)': {
paper: {
width: '70%',
left: '15% !important',
},
},
'#media screen and (max-width: 480px)': {
paper: {
width: '100%',
left: '0% !important',
height: '350px',
},
},
}), { name: 'ExperiencesStyle' });
*/const Experiences = memo(
(props) => {
const { className } = props;
const classes = useStyles(props);
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = (event) => {
setAnchorEl(anchorEl ? null : event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
// const open = Boolean(anchorEl);
const experience = useCallback((img, title, id, popoverCategory, anchorEl, classes, handleClick) => (
<div
className="experience"
aria-describedby={id}
id={id}
onClick={handleClick}
onKeyDown={handleClick}
role="button"
tabIndex="0"
>
<img
data-sizes="auto"
className="lazyload"
data-src={img}
alt={title}
/>
<div className="experience-title">
<Typography
color="textSecondary"
variant="subtitle2"
className="highlight highlight1"
display="inline"
>
{ title }
</Typography>
</div>
<Popover
id={id}
open={anchorEl && anchorEl.id === id}
anchorEl={anchorEl}
onClose={handleClose}
classes={{paper: clsx(classes.paper, classes[id])}}
>
<div>
<Button onClickAway={handleClose}>x</Button>
<div>
{
popoverCategory.map(url => (
<img
key={url}
data-sizes="auto"
className="lazyload"
src={url}
alt={title}
/>
))
}
</div>
</div>
</Popover>
</div>
), []);
return (
<div className={clsx(classes.root, className)}>
<div className="experiences-column col1">
{experience(gastronomia, 'GASTRONOMÍA', 'gastronomia', gastronomiaExperiences)}
{experience(giftcard, 'GIFT CARD', 'giftcard', giftcardExperiences)}
{experience(deporte, 'DEPORTE', 'deporte', deporteExperiences)}
</div>
<div className="experiences-column col2">
{experience(productos, 'PRODUCTOS', 'productos', productosExperiences)}
{experience(diversion, 'DIVERSIÓN', 'diversion', diversionExperiences)}
{experience(belleza, 'BELLEZA', 'belleza', bellezaExperiences)}
</div>
</div>
);
},
);
and the error is:
TypeError: Cannot read property 'paper' of undefined
referring to this line
classes={{paper: clsx(classes.paper, classes[id])}}
where I add the classes to the paper class of the popover.
I am not used to useCallback and new to react so I am lost.
const experience = useCallback((img, title, id, popoverCategory, anchorEl, classes, handleClick) => (
The function you have created expects 7 things to be passed into it. But when you use it, you only pass in 4:
experience(gastronomia, 'GASTRONOMÍA', 'gastronomia', gastronomiaExperiences)
So the remaining 3 are all undefined inside the function. the anchorEl, classes, and handleClick variables defined at the top of your component are not visible inside experience, because those variable are being "shadowed".
So you could stop shadowing the variables by simply removing the last 3 arguments from your function definition:
const experience = useCallback((img, title, id, popoverCategory) => (
However, i do have to express that useCallback doesn't seem to be doing anything for you. The benefit of useCallback is that you can have the experience variable be the same reference from one render to the next, but that doesn't seem to be a feature that you need. Your component never tries to compare references of experience, nor is experience passed to any other component where it might be checked in a shouldComponentUpdate or React.memo.
So i would recommend deleting the useCallback entirely:
const experience = (image, title, id, popoverCategory) => (
so basically what I want to reach is to get dimensions of the div element inside return method of component. I get reference to this by ref and I want to get its width and height with getBoundingClientRect() but there is error: Uncaught TypeError: Cannot read property 'getBoundingClientRect' of undefined. I also tried offsetWidth and offsetHeight.
Here is my code:
import React from 'react';
import ReactDOM from 'react-dom';
import Style from 'style-it';
var Ink = require('react-ink');
import FontIcon from '../FontIcon/FontIcon';
var IconButton = React.createClass({
getInitialState() {
return {
iconStyle: this.props.iconStyle,
style: this.props.style,
cursorPos: {},
};
},
extend(obj, src) {
Object.keys(src).forEach(function(key) { obj[key] = src[key]; });
return obj;
},
Tooltip() {
var box = this.refs.button.getBoundingClientRect(),
Height = box.clientHeight,
tooltipStyle = {
};
return <div className="tooltip" style={tooltipStyle}>{this.props.tooltip}</div>;
},
showTooltip(){
},
removeTooltip(){
},
render() {
var _props = this.props,
Tooltip = this.Tooltip,
opts,
disabled = false,
rippleOpacity,
outterStyleMy = {
border: "none",
outline: "none",
padding: "8px 10px",
backgroundColor: "red",
borderRadius: 100 + "%",
cursor: "pointer",
},
iconStyleMy = {
fontSize: 12 + "px",
textDecoration: "none",
textAlign: "center",
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
},
rippleStyle = {
color: "rgba(0,0,0,0.5)",
};
if (_props.disabled || _props.disableTouchRipple) {
rippleStyle.opacity = 0;
};
if (_props.disabled) {
disabled = true;
};
if (this.state.labelStyle) {
iconStyleMy = this.state.iconStyle;
};
if (this.state.style) {
outterStyleMy = this.state.style;
};
if (_props.href) {
opts.href = _props.href;
};
var buttonStyle = this.extend(outterStyleMy, iconStyleMy);
return(
<Style>
{`
.IconButton{
position: relative;
}
.IconButton:disabled{
color: ${_props.disabledColor};
}
.btnhref{
text-decoration: none;
}
`}
<a {...opts} className="btnhref" >
<Tooltip />
<button ref="button" className={"IconButton" + _props.className} disabled={disabled} style={buttonStyle}
onMouseEnter={this.showTooltip} onMouseLeave={this.removeTooltip} >
<Ink background={true} style={rippleStyle} opacity={rippleOpacity} />
<FontIcon className={_props.iconClassName}/>
</button>
</a>
</Style>
);
}
});
ReactDOM.render(
<IconButton href="" className="" iconStyle="" style="" iconClassName="face" disabled="" disableTouchRipple="" tooltip="aaaaa" />,
document.getElementById('app')
);
So... I don't know how to accomplish this.
You can't get a reference to a DOM node before it was rendered.
Do your this.refs.button.getBoundingClientRect() in the componentDidMount lifecycle method to be sure it was rendered and you can get a reference to it.