In my code im trying to get the div height by clientHeight by using onLoad. ComponentDdiMount doesnt give the clientHeight in this case as images load in the div.The code works fine for me.But when i try gatsby build i get the window error as im using onLoad. Is there any workaround for this?
export default class Mainpageanimation extends React.Component {
constructor(props) {
super(props);
this.topref = React.createRef();
}
load = () =>{
const centerheight = this.topref.current.clientHeight;
//animate div
}
render(){
return (
<div className="topdiv" ><img src={require("image.png") } ref={this.topref} onLoad=
{this.load}/></div>
);
}
}
Try including onLoad property only when the code is running in the browser
export default class Mainpageanimation extends React.Component {
constructor(props) {
super(props);
this.topref = React.createRef();
}
load = () =>{
const centerheight = this.topref.current.clientHeight;
//animate div
}
render(){
return (
<div
className="topdiv" >
<img src={require("image.png") } ref={this.topref}
{...typeof window !== 'undefined' ? {onLoad: this.load} : {}}
/>
</div>
);
}
}
With gatsby build, rather than gatsby develop, some objects such as window or document are not defined at the point your code is requesting them. The usual errors are fixed by checking if the window is defined in a componentDidMount lifecycle/useEffect hook before triggering your function. From Gatsby's documentation:
Some of your code references “browser globals” like window or
document. If this is your problem you should see an error above like
“window is not defined”. To fix this, find the offending code and
either a) check before calling the code if window is defined so the
code doesn’t run while Gatsby is building (see code sample below) or
b) if the code is in the render function of a React.js component, move
that code into a componentDidMount lifecycle or into a useEffect hook,
which ensures the code doesn’t run unless it’s in the browser.
So, my first approach will be checking if the window is defined:
load = () =>{
let centerHeight;
if (typeof window !== undefined){
centerHeight= this.topref.current.clientHeight;
//animate div
}
}
If this doesn't work, I would try to change your onLoad function and use it in a componentDidMount directly:
export default class Mainpageanimation extends React.Component {
constructor(props) {
super(props);
this.topref = React.createRef();
}
componentDidMount(){
let centerHeight;
if (typeof window !== undefined){
centerHeight= this.topref.current.clientHeight;
//animate div
}
}
render(){
return (
<div className="topdiv" ><img src={require("image.png") } ref={this.topref} /></div>
);
}
}
Besides the answer, I think you should bind your reference. In a functional component arrow functions does it automatically but in a stateful component like yours, you must do it manually. In your constructor:
this.topref= this.topref.bind(this);
You can check for further information in Refs and the DOM by React documentation.
I've been working on focusing on ListView component.
Here's my code
class MyListView extends Component {
static propTypes = {
navigator: PropTypes.object.isRequired,
};
componentDidMount() {
this.listView.focus()
}
render() {
return (
<ListView
ref={ref => { this.listView = ref }}
contentContainerStyle={styles.listView}
dataSource={cloneWithRows(listDataSource)}
renderRow={(data) => <MyListViewCell
...
/>}
/>
)
}
}
export default MyListView
but it just returns an error like "this.listView.focus is not a function"
I've searched lots of articles but I couldn't find the solution.
Any ideas?
componentDidMount does not guarantee that ref prop is called before componentDidMount is called. You should change for componentDidMount code to following:
componentDidMount() {
requestAnimationFrame(()=>{
this.listView.focus()
})
}
Also if you decide to use requestAnimationFrame function. It will be better you use it using TimerMixin (see TimerMixin doc). Use react-mixin to implement Mixin in ES6 as suggested in TimerMixin doc.
I'm trying to add onscroll event handler to specific dom element. Look at this code:
class ScrollingApp extends React.Component {
...
_handleScroll(ev) {
console.log("Scrolling!");
}
componentDidMount() {
this.refs.list.addEventListener('scroll', this._handleScroll);
}
componentWillUnmount() {
this.refs.list.removeEventListener('scroll', this._handleScroll);
}
render() {
return (
<div ref="list">
{
this.props.items.map( (item) => {
return (<Item item={item} />);
})
}
</div>
);
}
}
Code is quite simple, and as you can see, I want to handle div#list scrolling. When I was running this example, it isn't working. So I tried bind this._handleScroll on render method directly, it doesn't work either.
<div ref="list" onScroll={this._handleScroll.bind(this)> ... </div>
So i opened chrome inspector, adding onscroll event directly with:
document.getElementById('#list').addEventListener('scroll', ...);
and it is working! I don't know why this happens. Is this a bug of React or something? or did I missed something? Any device will very appreciated.
The root of the problem is that this.refs.list is a React component, not a DOM node. To get the DOM element, which has the addEventListener() method, you need to call ReactDOM.findDOMNode():
class ScrollingApp extends React.Component {
_handleScroll(ev) {
console.log("Scrolling!");
}
componentDidMount() {
const list = ReactDOM.findDOMNode(this.refs.list)
list.addEventListener('scroll', this._handleScroll);
}
componentWillUnmount() {
const list = ReactDOM.findDOMNode(this.refs.list)
list.removeEventListener('scroll', this._handleScroll);
}
/* .... */
}
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({...});
}
EDIT: My mistake, my webpack hotloader was caching the old js for some reason every time I ran a build. Reset and rebuilt and it seems to be working now.
I'm trying to create a simple searchbox using es6 style class declaration in a yahoo fluxible react app. I'm working off the todo example, converting it to es6 style syntax and I'm getting an error on this.setState in the _onChange method. I've bound the functions to "this" in the constructor but I'm still getting the error.
import React from 'react';
import searchProducts from '../actions/searchProducts';
const ENTER_KEY_CODE = 13;
class SearchBox extends React.Component {
static contextTypes = {
executeAction: React.PropTypes.func.isRequired
};
static propTypes = {
text: React.PropTypes.string
};
static defaultProps = {
text:''
};
constructor(props) {
super(props);
this.state = {
text: props.text
};
this._onChange = this._onChange.bind(this);
this._onKeyDown = this._onKeyDown.bind(this);
}
render() {
return (
<input
className="search-box"
name="search-keyword"
value={this.state.text}
onChange={this._onChange}
onKeyDown={this._onKeyDown}
/>
);
}
_onChange(event, value) {
console.log( event.target.value);
//error is here///////////////////////////////////////////////////
this.setState({text: event.target.value});
}
_onKeyDown(event) {
if (event.keyCode === ENTER_KEY_CODE) {
event.preventDefault();
event.stopPropagation();
var text = this.state.text.trim();
if (text) {
this.context.executeAction(searchProducts, {
text: text
});
}
this.setState({text: ''});
}
}
}
export default SearchBox;
Edit: Disregard. I completely missed the part of the constructor where you bound the methods. Hmm.. What does the error message say exactly?
Have a look at section three of this article about refactoring react components to es6 classes.
When using the React.createClass({componentObjectLiteral}) syntax, React binds your object methods to the component instance, so that when your _onChange method gets called as your input's onChange callback function, the this keyword in your _onChange method is bound to your component.
React does not auto-bind your methods for you, so you have to do it yourself. Change your JSX to onChange={this._onChange.bind(this)}
psigns is correct React.createClass() automatically binds the methods to the component instance for you. This is not the case when you use the class syntax in React.
But there is a very neat possibility when you combine property initializers with arrow functions:
class SearchBox extends React.Component {
// …
_onChange = (event, value) => {
// …
// this will be bound to component
this.setState({text: event.target.value});
}
// …
}
Then you can use the method like you did before in the jsx part:
onChange={this._onChange} // without .bind(this)
I learned this from reading Steven Luscher's excellent post.