Reactjs, how to get dimensions of element - javascript

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.

Related

how to change div background color by using onMouseEnter only on currently moused over element in React

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

React useRef() not working onMouseOver to change backgroundColor of div

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>
);
};

Changing an object value from a Class

I have a js file handling my css where I am trying to change the value of an object, but the value stays the same.
let inputBoxInner = {
width: "80%",
marginTop: 5,
alignItems: "center",
color: "#397185",
cursor: "text",
height: 36,
border: "1px solid #80cfc6",
visibility: "visible",
opacity: 0.2,
setOpacity: function (e) {
this.opacity = e
};
};
module.exports = {
inputBoxInner
};
import React, {Component} from "react";
import {inputBoxInner} from "../css/style.js";
export default class Input extends Component {
state = {
borderOpacity: 1,
id: ""
};
return(
<div
className="input"
onClick={(e) => {
inputBoxInner.setOpacity(this.state.borderOpacity);
this.setState({id: e.target.className});
}}
style={inputBoxInner}
/>
);
};
I assume the "this.opacity" is only returning a reference and not modifying the actual object and I am unsure of how to make this object mutable.
How would I go about changing this value?
You should save a clicked state in the state and set opacity depending on it.
state = {
borderOpacity: 1,
id: "",
isClicked: false
};
return(
<div
className="input"
onClick={(e) => { this.setState({id: e.target.className, isClicked: true }); }}
style={{...inputBoxInner, opacity: this.state.isClicked ?
this.state.borderOpacity : inputBoxInner.opacity}}
/>
);

Can I set dynamic class name in React Jss with destructing

I am using something like this <span className={`${status_icon} ${classes[`${status}`]}`} /> but I want to destructor the object and use it. Is there any way to do it ?
Currently I have
const classes = useStyles();
const { status_icon } = classes;
I want to use it like this
const {status_icon, learning_in_progress, not_started} = useStyles();
Source code - CodeSandBox
import React from "react";
import { createUseStyles } from "react-jss";
export default function App() {
return (
<>
<h3>
<LearnGridStatus status="not_started" />
Not Started
</h3>
<h3>
<LearnGridStatus status="learning_in_progress" />
In Progress
</h3>
</>
);
}
function LearnGridStatus({ status }) {
const styles = {
status_icon: {
display: "inline-block",
marginRight: "8px",
width: "15px",
height: "15px",
verticalAlign: "middle",
borderRadius: "50%"
},
not_started: {
background: "#6a7887"
},
learning_in_progress: {
background: "#3dc3ff"
}
};
const useStyles = createUseStyles(styles);
const classes = useStyles();
const { status_icon } = classes;
return (
<span className={`${status_icon} ${classes[`${status}`]}`} />
);
}
Yes, you can use Computed property names:
const styles = {
status_icon: {
display: "inline-block",
marginRight: "8px",
width: "15px",
height: "15px",
verticalAlign: "middle",
borderRadius: "50%"
},
not_started: {
background: "#6a7883"
},
learning_in_progress: {
background: "#3dc3ff"
}
};
const useStyles = createUseStyles(styles);
function LearnGridStatus({ status }) {
const { status_icon, [status]: currentStatus } = useStyles();
return <span className={`${status_icon} ${currentStatus}`} />;
}
Working example:

DRY up React code. Multiple button components that change the parent component's background

I'm sure there is a way to DRY the code. All changeColor() is doing is changing the background color of the parent component .
import { Play } from "./play";
import { Hello } from "./hello";
import { styles } from "./styles";`
export class Buttons extends React.Component {
constructor(props) {
super(props);
this.state = {
color: styles.container
};
this.changeColor = this.changeColor.bind(this);
this.changeColor1 = this.changeColor1.bind(this);
this.changeColor2 = this.changeColor2.bind(this);
}
changeColor(newColor) {
this.setState({
color: styles.backPlay
});
}
changeColor1(newColor) {
this.setState({
color: styles.backTime
});
}
changeColor2(newColor) {
this.setState({
color: styles.backHello
});
}
render() {
return (
<div style={this.state.color}>
<Play onClick={this.changeColor} />
<Time onClick={this.changeColor1} />
<Hello onClick={this.changeColor2} />
</div>
);
}
}
Here is the styles page, which I also think can use a little DRY. Container, backPlay, backTime and backHello all represent the same container but a different background.
styles.js
export var styles = {
loc: {
padding: 25,
margin: 40,
fontWeight: "bold",
textAlign: "center"
},
green: {
color: "green",
background: "#59D13E"
},
red: {
color: "yellow",
background: "#A9A81D"
},
blue: {
color: "blue",
background: "#34BEE3"
},
container: {
display: "inline-block",
textAlign: "center",
marginTop: 50,
padding: 40
},
backPlay: {
display: "inline-block",
textAlign: "center",
background: "yellow",
marginTop: 50,
padding: 40
},
backTime: {
display: "inline-block",
textAlign: "center",
background: "blue",
marginTop: 50,
padding: 40
},
backHello: {
display: "inline-block",
textAlign: "center",
background: "green",
marginTop: 50,
padding: 40
},
mainCont: {
height: "100vh",
textAlign: "center",
background: "#FFA692"
}
};
UPDATE
I found a better way to DRY up this code. By using one button component and manipulating it's state. Let me know if there is even a better way to do this.
ButtonContainer.js
import React from 'react'
import Button from './Button'
export default class ButtonContainer extends React.Component {
state = {
colors: ['red', 'blue', 'green']
}
toggleClass = (color, id) => {
let colors = [...this.state.colors]
const newColors = colors.map((newColor, index) => {
if (id === index) {
const copyMap = { 0: 'red', 1: 'blue', 2: 'green' }
const copy = color === 'not' ? copyMap[index] : 'not'
return copy
} else {
return newColor
}
})
this.setState({ colors: newColors })
}
render() {
return (
<div className='button-container'>
{this.state.colors.map((color, index) =>
<Button
toggleClass={this.toggleClass}
key={index}
id={index}
name={color}
/>
)}
</div>
)
}
}
Button.js
import React from 'react'
const Button = (props) => (
<button
className={`button-component ${props.name}`}
onClick={() => props.toggleClass(props.name, props.id)}
>
{props.name}
</button>
)
export default Button
_button-container.scss
.button-container {
margin: 10rem auto;
text-align: center;
}
_button.scss
.button-component {
padding: 4rem;
margin: 0 2rem;
}
.red {
background: red;
}
.blue {
background: blue;
}
.green {
background: green;
}
.not {
background: none;
}
You can use .bind() to pre-bind arguments to a function before passing it down as a prop:
export class Buttons extends React.Component {
state = {
color: styles.container
};
changeColor = newColor => {
this.setState({
color: newColor
});
};
render() {
return (
<div style={this.state.color}>
<Play onClick={this.changeColor.bind(this, styles.backPlay)} />
<Time onClick={this.changeColor.bind(this, styles.backTime)} />
<Hello onClick={this.changeColor.bind(this, styles.backHello)} />
</div>
);
}
}
You can also remove your constructor and use fat arrow functions to autobind your methods to the component.
styles.js
const container = {
display: "inline-block",
textAlign: "center",
marginTop: 50,
padding: 40
};
export const styles = {
loc: {
padding: 25,
margin: 40,
fontWeight: "bold",
textAlign: "center"
},
green: {
color: "green",
background: "#59D13E"
},
red: {
color: "yellow",
background: "#A9A81D"
},
blue: {
color: "blue",
background: "#34BEE3"
},
container,
backPlay: {
...container,
background: "yellow"
},
backTime: {
...container,
background: "blue"
},
backHello: {
...container,
background: "green"
},
mainCont: {
height: "100vh",
textAlign: "center",
background: "#FFA692"
}
};
You can use the es6 spread operator to copy the contents of styles.container to each style, and then override the color property.
Since all of your color changing functions are very similar, you can pass in the name of the style you want to apply and use it inside the function, thus saving you the repetition.
changeColor(attr) {
this.setState({
color: styles[attr]
});
}
render() {
return (
<div style={this.state.color}>
<Play onClick={() => this.changeColor('backPlay')} />
<Time onClick={() => this.changeColor('backTime')} />
<Hello onClick={() => this.changeColor('backHello')} />
</div>
);
}

Categories

Resources