onScroll React cannot trigger - reactjs

why if i using onScroll react version 16 cannot fire how to make it work. because i want using onScroll than onWheel?.
import ReactDom from 'react-dom'
constructor(props) {
super(props);
this._fireOnScroll = this.fireOnScroll.bind(this);
}
fireOnScroll() {
console.log('Fire!');
}
componentDidMount() {
const elem = ReactDOM.findDOMNode(this.refs.elementToFire);
elem.addEventListener('scroll', this._fireOnScroll, true);
}
componentWillUnmount() {
const elem = ReactDOM.findDOMNode(this.refs.elementToFire);
elem.removeEventListener('scroll', this._fireOnScroll);
}
render() {
return (
<div ref="elementToFire">
<AnotherComponent imagesUrl={Array(100)}}>}
</div>
</div>
);
}

Your div is empty. scroll only fires if actual scrolling did happen.
So, your div needs to be smaller than its content and needs to show scrollbars.
See this pen for a working example.
However, there is no reason for using a ref. Simply use onScroll:
<div style={{height: 75, width: 100, overflow:'scroll'}} onScroll={this.fireOnScroll}>
See this pen for a working example.

Your ref is not properly assigned to the div it should be
<div ref = {this.elementToFire} >
If not works then try
const elem = ReactDOM.findDOMNode(this.refs.elementToFire.current);
Also from the code you have given O guess you missed to create a ref object also
this.elementToFire = React.createRef();
Hope it will work

Related

reactjs function to find window size and onClick subtract 80% of it each time

I'm trying to make a function that will on each click move my container 80% of window width to the left.
I am using this in my state
left: 0
and this is part of the style of my container that I want to be moved, like this
<div className="horizontal_container" style={{ left: props.left }}>
and I want to use a function that I'm passing via props that looks like this
pomeranjeGalerije = () => {
const {left} = this.state;
left -= window.innerWidth*0,8;
this.setState({left: left});
}
this is how I pass the function via the prop
<ContainerTheatre klikLevo={this.pomeranjeGalerije}/>
and this is the actual button
<img onClick={props.klikLevo} src="xx" alt="right_arrow" />
I get the error message
Expected an assignment or function call and instead saw an expression
for the line left -= window.innerWidth*0,8;
can you please help me get the function right?
you have several problems in your code.
You have to use the decimal point instead of a comma. So use 0.8 instead of 0,8.
You also have to access your left-value from state.left instead of props.left.
Your calculation of left doesn't make much sense. I change that so your div has the initial value of window.innerWidth and shrinks to 80% of its previous value on each button click.
:
import React from 'react';
export default class YourComponent extends Component {
constructor(props) {
super(props);
this.state = { left: window.innerWidth };
this.buttonClick = this.buttonClick.bind(this);
}
buttonClick() {
let newWidth = this.state.left * 0.8;
console.log('new width is', newWidth);
this.setState({left: newWidth });
}
render() {
return (<div style={{"background": "#ff0000", "width": this.state.left + "px", "left": this.state.left}}>
<button onClick={this.buttonClick}>Shrink to 80% of Width</button>
</div>)
}
}
I'm not sure exactly what you are looking for, but this code will create a container component that when clicked on will shrink to 80% of the window. If it's not exactly the same as what you want, it can at least give you an idea of what to do.
import React from 'react';
class App extends React.Component {
render () {
return (
<div>
<Container/>
</div>
)
}
}
class Container extends React.Component {
constructor(props){
super(props)
this.state = {
left:window.innerWidth
}
}
onClick=()=>{
var left = (this.state.left)-window.innerWidth*0.2;
this.setState({left: left});
console.log(left)
}
render () {
return(
<div style={{width:this.state.left+"px",backgroundColor:'red'}} onClick={this.onClick}>
<h1>Hello</h1>
</div>
)
}
}
export default App;

get height of image on load and send to parent

I am trying to get the height of an image when it has loaded and send it back to the parent component, but it is causing infinite rerendering.
This is a prototype of my code:
import MyImage from './images/myImage.jpg';
class Image extends React.Component {
constructor(props) {
super(props);
this.state = {
height: 0
}
}
getHeight = (e) => {
const height = e.target.getBoundingClientRect().height;
this.setState({
height: height
});
this.props.setUnitHeight(height);
}
render() {
const image = this.props.image;
return (
<img src={image.name} onLoad={(e)=>{this.getHeight(e)}} />;
);
}
}
class App extends Component {
constructor(props) {
super(props);
const initUnit = 78.4;
this.state = {
unit: initUnit
}
}
setUnitHeight = (height) => {
this.setState({
unit: height
});
}
render() {
return (
<div>
<Image image={MyImage} setUnitHeight={this.setUnitHeight} />
</div>
);
}
}
I have tried sending unit as a prop and then checking in shouldComponentUpdate whether it should be rerender or not, but that did nothing.
The issue you are having is that React by default re-renders the component every time you call this.setState. In your case what this is happening:
You load your Image component
It loads the <img> tag and fires the onLoad function
The onLoad function calls this.setState
Repeat these steps forever
You should take a look at the React's lifecycle components methods (https://reactjs.org/docs/react-component.html#the-component-lifecycle) to understand this better.
My suggestion is: do not keep the image height in the state, unless you really need it. If you really need to maintain it in the state for some reason you can use the lifecycle method shouldComponentUpdate (https://reactjs.org/docs/react-component.html#shouldcomponentupdate`) to prevent it from rendering.
Your code seems redundant, setState({}) isn't necessary in <Image> class. If you are using the same props throughout the app, then you should be setting it at one place and be using the same prop all over. For example -
getHeight = (e) => {
const height = e.target.getBoundingClientRect().height;
//setState not needed here
this.props.setUnitHeight(height);
}
That should do it.
P.S: Do check if your this references aren't going out of scope.

ReactJS - Add custom event listener to component

In plain old HTML I have the DIV
<div class="movie" id="my_movie">
and the following javascript code
var myMovie = document.getElementById('my_movie');
myMovie.addEventListener('nv-enter', function (event) {
console.log('change scope');
});
Now I have a React Component, inside this component, in the render method, I am returning my div. How can I add an event listener for my custom event? (I am using this library for TV apps - navigation )
import React, { Component } from 'react';
class MovieItem extends Component {
render() {
if(this.props.index === 0) {
return (
<div aria-nv-el aria-nv-el-current className="menu_item nv-default">
<div className="indicator selected"></div>
<div className="category">
<span className="title">{this.props.movieItem.caption.toUpperCase()}</span>
</div>
</div>
);
}
else {
return (
<div aria-nv-el className="menu_item nv-default">
<div className="indicator selected"></div>
<div className="category">
<span className="title">{this.props.movieItem.caption.toUpperCase()}</span>
</div>
</div>
);
}
}
}
export default MovieItem;
Update #1:
I applied all the ideas provided in the answers. I set the navigation library to debug mode and I am able to navigate on my menu items only based on the keyboard (as you can see in the screenshot I was able to navigate to Movies 4) but when I focus an item in the menu or press enter, I dont see anything in the console.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class MenuItem extends Component {
constructor(props) {
super(props);
// Pre-bind your event handler, or define it as a fat arrow in ES7/TS
this.handleNVFocus = this.handleNVFocus.bind(this);
this.handleNVEnter = this.handleNVEnter.bind(this);
this.handleNVRight = this.handleNVRight.bind(this);
}
handleNVFocus = event => {
console.log('Focused: ' + this.props.menuItem.caption.toUpperCase());
}
handleNVEnter = event => {
console.log('Enter: ' + this.props.menuItem.caption.toUpperCase());
}
handleNVRight = event => {
console.log('Right: ' + this.props.menuItem.caption.toUpperCase());
}
componentDidMount() {
ReactDOM.findDOMNode(this).addEventListener('nv-focus', this.handleNVFocus);
ReactDOM.findDOMNode(this).addEventListener('nv-enter', this.handleNVEnter);
ReactDOM.findDOMNode(this).addEventListener('nv-right', this.handleNVEnter);
//this.refs.nv.addEventListener('nv-focus', this.handleNVFocus);
//this.refs.nv.addEventListener('nv-enter', this.handleNVEnter);
//this.refs.nv.addEventListener('nv-right', this.handleNVEnter);
}
componentWillUnmount() {
ReactDOM.findDOMNode(this).removeEventListener('nv-focus', this.handleNVFocus);
ReactDOM.findDOMNode(this).removeEventListener('nv-enter', this.handleNVEnter);
ReactDOM.findDOMNode(this).removeEventListener('nv-right', this.handleNVRight);
//this.refs.nv.removeEventListener('nv-focus', this.handleNVFocus);
//this.refs.nv.removeEventListener('nv-enter', this.handleNVEnter);
//this.refs.nv.removeEventListener('nv-right', this.handleNVEnter);
}
render() {
var attrs = this.props.index === 0 ? {"aria-nv-el-current": true} : {};
return (
<div ref="nv" aria-nv-el {...attrs} className="menu_item nv-default">
<div className="indicator selected"></div>
<div className="category">
<span className="title">{this.props.menuItem.caption.toUpperCase()}</span>
</div>
</div>
)
}
}
export default MenuItem;
I left some lines commented because in both cases I am not able to get the console lines to be logged.
Update #2: This navigation library does not work well with React with its original Html Tags, so I had to set the options and rename the tags to use aria-* so it would not impact React.
navigation.setOption('prefix','aria-nv-el');
navigation.setOption('attrScope','aria-nv-scope');
navigation.setOption('attrScopeFOV','aria-nv-scope-fov');
navigation.setOption('attrScopeCurrent','aria-nv-scope-current');
navigation.setOption('attrElement','aria-nv-el');
navigation.setOption('attrElementFOV','aria-nv-el-fov');
navigation.setOption('attrElementCurrent','aria-nv-el-current');
If you need to handle DOM events not already provided by React you have to add DOM listeners after the component is mounted:
Update: Between React 13, 14, and 15 changes were made to the API that affect my answer. Below is the latest way using React 15 and ES7. See answer history for older versions.
class MovieItem extends React.Component {
componentDidMount() {
// When the component is mounted, add your DOM listener to the "nv" elem.
// (The "nv" elem is assigned in the render function.)
this.nv.addEventListener("nv-enter", this.handleNvEnter);
}
componentWillUnmount() {
// Make sure to remove the DOM listener when the component is unmounted.
this.nv.removeEventListener("nv-enter", this.handleNvEnter);
}
// Use a class arrow function (ES7) for the handler. In ES6 you could bind()
// a handler in the constructor.
handleNvEnter = (event) => {
console.log("Nv Enter:", event);
}
render() {
// Here we render a single <div> and toggle the "aria-nv-el-current" attribute
// using the attribute spread operator. This way only a single <div>
// is ever mounted and we don't have to worry about adding/removing
// a DOM listener every time the current index changes. The attrs
// are "spread" onto the <div> in the render function: {...attrs}
const attrs = this.props.index === 0 ? {"aria-nv-el-current": true} : {};
// Finally, render the div using a "ref" callback which assigns the mounted
// elem to a class property "nv" used to add the DOM listener to.
return (
<div ref={elem => this.nv = elem} aria-nv-el {...attrs} className="menu_item nv-default">
...
</div>
);
}
}
Example on Codepen.io
You could use componentDidMount and componentWillUnmount methods:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class MovieItem extends Component
{
_handleNVEvent = event => {
...
};
componentDidMount() {
ReactDOM.findDOMNode(this).addEventListener('nv-event', this._handleNVEvent);
}
componentWillUnmount() {
ReactDOM.findDOMNode(this).removeEventListener('nv-event', this._handleNVEvent);
}
[...]
}
export default MovieItem;
First off, custom events don't play well with React components natively. So you cant just say <div onMyCustomEvent={something}> in the render function, and have to think around the problem.
Secondly, after taking a peek at the documentation for the library you're using, the event is actually fired on document.body, so even if it did work, your event handler would never trigger.
Instead, inside componentDidMount somewhere in your application, you can listen to nv-enter by adding
document.body.addEventListener('nv-enter', function (event) {
// logic
});
Then, inside the callback function, hit a function that changes the state of the component, or whatever you want to do.
I recommend using React.createRef() and ref=this.elementRef to get the DOM element reference instead of ReactDOM.findDOMNode(this). This way you can get the reference to the DOM element as an instance variable.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class MenuItem extends Component {
constructor(props) {
super(props);
this.elementRef = React.createRef();
}
handleNVFocus = event => {
console.log('Focused: ' + this.props.menuItem.caption.toUpperCase());
}
componentDidMount() {
this.elementRef.addEventListener('nv-focus', this.handleNVFocus);
}
componentWillUnmount() {
this.elementRef.removeEventListener('nv-focus', this.handleNVFocus);
}
render() {
return (
<element ref={this.elementRef} />
)
}
}
export default MenuItem;
Here is a dannyjolie more detailed answer without need of component reference but using document.body reference.
First somewhere in your app, there is a component method that will create a new custom event and send it.
For example, your customer switch lang.
In this case, you can attach to the document body a new event :
setLang(newLang) {
// lang business logic here
// then throw a new custom event attached to the body :
document.body.dispatchEvent(new CustomEvent("my-set-lang", {detail: { newLang }}));
}
Once that done, you have another component that will need to listen to the lang switch event. For example, your customer is on a given product, and you will refresh the product having new lang as argument.
First add/remove event listener for your target component :
componentDidMount() {
document.body.addEventListener('my-set-lang', this.handleLangChange.bind(this));
}
componentWillUnmount() {
document.body.removeEventListener('my-set-lang', this.handleLangChange.bind(this));
}
then define your component my-set-langw handler
handleLangChange(event) {
console.log("lang has changed to", event.detail.newLang);
// your business logic here .. this.setState({...});
}

Get the height of a Component in React

I have 4 columns, none of whose height is fixed, and I need to find the height of these columns so that the height of the largest column can be set to the other three. How can I do this with React and not using the 'minHeight' css?
I am a newbie in React and the closest question I found here was ReactJS get rendered component height.
Also I found this link which says that this could be done by getting the DOMNode and using the Refs, but I'm with no success.
You can just use the ref callback and access the DOMNode inside it.
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
height: null
};
this.columns = ['hello',
'this is a bit more text',
'this is a bit more text ... and even more'];
}
render(){
return <div ref={(node) => this.calcHeight(node)}>
{
this.columns.map((column) => {
return <div style={{height: this.state.height}}>{column}</div>
})
}
</div>;
}
calcHeight(node) {
if (node && !this.state.height) {
this.setState({
height: node.offsetHeight
});
}
}
}
React.render(<Example />, document.getElementById('container'));
Working example on jsfiddle: http://jsfiddle.net/vxub45kx/4/
Also look here: https://facebook.github.io/react/docs/more-about-refs.html

ReactJS get rendered component height

I'm attempting to integrate or create a React version of https://github.com/kumailht/gridforms, to do so I need to normalize the height of the columns inside of the row. The original takes the height of the grid row and applies it to the children columns.
I had planned to get the height of the row and then map it to a property of the child, though from my attempts I'm thinking this might not be the ideal way or even possible?
Below is my current code.
GridRow = React.createClass({
render(){
const children = _.map(this.props.children, child => {
child.props.height = // somehow get row component height
return child
})
return (<div data-row-span={this.props.span} {...this.props}>
{children}
</div>)
}
})
GridCol = React.createClass({
render(){
return (<div data-field-span={this.props.span} style={{height:this.props.height}} {...this.props}>
{this.props.children}
</div>)
}
})
I tested setting the style this way and it will work, however getting the height isn't.
EDIT: Fiddle:
https://jsfiddle.net/4wm5bffn/2/
A bit late with the answer but technically you can get element hight this way:
var node = ReactDOM.findDOMNode(this.refs[ref-name]);
if (node){
var calculatedHeight = node.clientHeight;
}
According to current React docs, the preferred use of refs is to pass it a callback rather than a string to be accessed elsewhere in this.refs.
So to get the height of a div (within a React.Component class):
componentDidMount() {
this.setState({ elementHeight: this.divRef.clientHeight });
}
render() {
return <div ref={element => this.divRef = element}></div>
}
Or it works this way, though I don't know if this is advisable since we set state in the render method.
getHeight(element) {
if (element && !this.state.elementHeight) { // need to check that we haven't already set the height or we'll create an infinite render loop
this.setState({ elementHeight: element.clientHeight });
}
}
render() {
return <div ref={this.getHeight}></div>;
}
Reference: https://facebook.github.io/react/docs/more-about-refs.html
Don't know about anyone else but I always have to get it on the next tick to be sure of getting the correct height and width. Feels hacky but guessing it's to do with render cycle but I'll take it for now. onLayout may work better in certain use cases.
componentDidMount() {
setTimeout(() => {
let ref = this.refs.Container
console.log(ref.clientHeight)
console.log(ref.clientWidth)
}, 1)
}
Here is an example of using refs and clientWidth/clientHeight:
import React, { Component } from 'react';
import MyImageSrc from './../some-random-image.jpg'
class MyRandomImage extends Component {
componentDidMount(){
let { clientHeight, clientWidth } = this.refs.myImgContainer;
console.log(clientHeight, clientWidth);
}
render() {
return (
<div ref="myImgContainer">
<img src={MyImageSrc} alt="MyClickable" />
</div>
);
}
}
export default MyRandomImage;
Note: this appears to work for width reliably, but not height. Will edit if I find a fix...
My personal opinion is to try and avoid using static and measured sizes like this if you can avoid it because it can complicate the application unnecessarily. But sometimes you cannot get around it. Your component will need to be mounted before you can get a size from it.
General approach:
Give the element a ref
When the element is rendered, grab the ref and call .clientHeight and/or .clientWidth
Put the values on the state or pass with props
Render the element that needs the size from the state variables
In your case you want to grab the size of a column you can do something like:
GridRow = React.createClass({
render(){
const children = _.map(this.props.children, child => {
child.props.height = // somehow get row component height
return child
})
return (<div data-row-span={this.props.span} {...this.props}>
<GridCol onSizeChange={(size) => {
//Set it to state or whatever
console.log("sizeOfCol", size);
}} />
</div>)
}
})
GridCol = React.createClass({
componentDidMount(){
//Set stizes to the local state
this.setState({
colH: this.col.clientHeight,
colW: this.col.clientWidth
});
//Use a callback on the props to give parent the data
this.props.onSizeChange({colH: this.col.clientHeight, colW: this.col.clientWidth})
}
render(){
//Here you save a ref (col) on the class
return (<div ref={(col) => {this.col = col}} data-field-span={this.props.span} style={{height:this.props.height}} {...this.props}>
<.... >
</div>)
}
})
According this answer sizes of a component can be turned out having zero width or height inside componentDidMount event handler. So I'm seeing some ways to solve it.
Handle the event on top-level React component, and either recalculate the sizes there, or redraw the specific child component.
Set the load event handler on the componentDidMount to handle loading the cells into the react component to recalculate the proper sizes:
componentDidMount = () => {
this.$carousel = $(this.carousel)
window.addEventListener('load', this.componentLoaded)
}
Then in the componentLoaded method just do what you need to do.
A bit more late, but I have an approach which can be used without using the getElementById method. A class based component could be created and the sample code can be used.
constructor(props) {
super(props);
this.imageRef = React.createRef();
}
componentDidMount(){
this.imageRef.current.addEventListener("load", this.setSpans);
}
setSpans = () => {
//Here you get your image's height
console.log(this.imageRef.current.clientHeight);
};
render() {
const { description, urls } = this.props.image;
return (
<div>
<img ref={this.imageRef} alt={description} src={urls.regular} />
</div>
);
}
Above solutions are good. I thought I'd add my own that helped me solve this issue + others discussed in this question.
Since as others have said a timeout function is unpredictable and inline css with javascript variable dependencies (ex. style={{height: `calc(100vh - ${this.props.navHeight}px)`}}) can alter the height of elements after the componentDidMount method, there must be an update after all of the elements and inline javascript-computed css is executed.
I wasn't able to find very good information on which elements accept the onLoad attribute in React, but I knew the img element did. So I simply loaded a hidden image element at the bottom of my react component. I used the onLoad to update the heights of referenced components elsewhere to yield the correct results. I hope this helps someone else.
_setsectionheights = () => {
this.setState({
sectionHeights: [
this.first.clientHeight,
this.second.clientHeight,
this.third.clientHeight,
]
});
}
render() {
return (
<>
<section
ref={ (elem) => { this.first = elem } }
style={{height: `calc(100vh - ${this.props.navHeight}px)`}}
>
...
</section>
...
<img style={{display: "none"}} src={..} onLoad={this._setsectionheights}/>
</>
);
}
For the sake of being thorough, the issue is that when the componentDidMount method is executed, it only considers external css (speculation here). Therefore, my section elements (which are set to min-height: 400px in external css) each returned 400 when referenced with the clientHeight value. The img simply updates the section heights in the state once everything before it has loaded.
I'd rather do it in componentDidUpdate, but by making sure a condition is met to prevent an infinite loop:
componentDidUpdate(prevProps, prevState) {
const row = document.getElementById('yourId');
const height = row.clientHeight;
if (this.state.height !== height) {
this.setState({ height });
}
}

Resources