I'm using the React-Starter-Kit and am having an issue with an onClick={this.handleClick} not firing in the browser.
What is happening: The Colorswatches.js component is loading and showing up in the browser but the onClick isn't working. No console logs are showing up.
What I think is the problem: Rendering everything server side and passing to client, client gets static react html with no event bindings.
How do I get the click event to work client side after server side rendering?
EDIT: Updating code with provided example from jgldev
EDIT 2: Added componentDidMount() function. With a console log, still not seeing the log on page load
EDIT 3: My issued was with another part of the React-starter-kit that was bombing out the client side re-render. I marked the first answer as correct.
src/component/ColorSwatches/ColorSwatches.js:
import React, { Component, PropTypes } from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import s from './ColorSwatches.scss';
class ColorSwatches extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
console.log('I was mounted');
}
handleClick(){
console.log('I was clicked');
}
render(){
let colorSlices = this.props.colorSlices;
let sku = this.props.sku;
let currentSkuIndex = 0
return (
<div className='pdpColors'>
<div className='colorSwatches' >
{ colorSlices.map((colorSlice, index) => {
return (
<div title={colorSlice.color} onClick={()=>this.handleClick()} key={index}>
<img src={colorSlice.swatchURL}/>
</div>
)
})}
</div>
</div>
)
}
}
export default withStyles(ColorSwatches,s);
My guess is that the following is going on:
Originally, this referred to the mounted DOM component, which is not what you want.
Wrapping the onClick to onClick={()=>this.handleClick()} is a step in the right direction, but not enough. this now refers to a react component, but not the right one. It is defined inside a .map function, so it refers to the colorSlice. Which is not what you want.
My advice take it one step further:
inside render, before your return statement, add the following line
let that = this; // this refers to ColorSwatches component, as intended
and inside the mapping function change onClick to:
onClick={that.handleClick} // no need to add the wrapper now
Hope this helps.
maybe try this,
first of all make a var that contains this. this should happen in your render()
var self = this;
Then on your onClick
onClick={self.handleClick.bind(this)}
This worked for me when i ran into this problem.
Hope it works out!
on the constructor:
this.handleClick = this.handleClick.bind(this)
Then, in the render function:
onClick={()=>this.handleClick()}
Related
We are wrapping a component library with React components, but in some cases the library manipulates the DOM tree in such a way that React will crash when trying to remove the React components.
Here's a sample that reproduces the issue:
function Sample ()
{
let [shouldRender, setShouldRender] = React.useState(true);
return (
<React.Fragment>
<button onClick={() => setShouldRender(!shouldRender)}>show/hide</button>
{ shouldRender && <Component /> }
</React.Fragment>
);
}
function Component ()
{
let ref = React.useRef();
React.useEffect(() => {
let divElement = ref.current;
someExternalLibrary.setup(divElement);
return () => someExternalLibrary.cleanup(divElement);
});
return <div ref={ref} id="div1">Hello world</div>;
}
ReactDOM.render(
<Sample />,
document.getElementById('container')
);
let someExternalLibrary = {
setup: function(divElement)
{
let beacon = document.createElement('div');
beacon.id = `beacon${divElement.id}`;
divElement.parentElement.replaceChild(beacon, divElement);
document.body.append(divElement);
},
cleanup: function(divElement)
{
let beacon = document.getElementById(`beacon${divElement.id}`);
beacon.parentElement.replaceChild(divElement, beacon);
}
}
You can find this sample on JSFiddle.
The above sample will render the Component which integrates with someExternalLibrary.
The external library moves the elements from inside the React component somewhere else.
Even if the external library puts back the element at its original location using a beacon, React will still complain when trying to remove the Component when you click on the show/hide button.
This will be the error
"Error: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
at removeChildFromContainer (https://unpkg.com/react-dom#17/umd/react-dom.development.js:10337:17)
at unmountHostComponents (https://unpkg.com/react-dom#17/umd/react-dom.development.js:21324:11)
at commitDeletion (https://unpkg.com/react-dom#17/umd/react-dom.development.js:21377:7)
at commitMutationEffects (https://unpkg.com/react-dom#17/umd/react-dom.development.js:23437:13)
at HTMLUnknownElement.callCallback (https://unpkg.com/react-dom#17/umd/react-dom.development.js:3942:16)
at Object.invokeGuardedCallbackDev (https://unpkg.com/react-dom#17/umd/react-dom.development.js:3991:18)
at invokeGuardedCallback (https://unpkg.com/react-dom#17/umd/react-dom.development.js:4053:33)
at commitRootImpl (https://unpkg.com/react-dom#17/umd/react-dom.development.js:23151:11)
at unstable_runWithPriority (https://unpkg.com/react#17/umd/react.development.js:2764:14)
at runWithPriority$1 (https://unpkg.com/react-dom#17/umd/react-dom.development.js:11306:12)"
An easy fix would be to wrap the existing HTML inside another DIV element so that becomes the root of the component, but unfortunately, that's not always possible in our project, so I need another solution.
What would be the best approach to solving this?
Is there a way to use a ReactFragment and re-associate the HTMLElement with the fragment during cleanup?
The cleanup function of useEffect, hence someExternalLibrary.cleanup(), is called after React has updated the DOM (removed the div from the DOM). It fails, because React is trying to remove a DOM node that someExternalLibrary.setup() removed (and someExternalLibrary.cleanup() will put back later).
In class components you can call someExternalLibrary.cleanup() before React updates the DOM. This would fix your code:
class Component extends React.Component {
constructor(props) {
super(props);
this.divElementRef = React.createRef();
}
componentDidMount() {
someExternalLibrary.setup(this.divElementRef.current);
}
componentWillUnmount() {
someExternalLibrary.cleanup(this.divElementRef.current);
}
render() {
return <div ref={this.divElementRef} id="div1">Hello world</div>;
}
}
The "extra div solution" fails for the same reason: Calling someExternalLibrary.cleanup() during the cleanup of useEffect() will mean the DOM has changed, but someExternalLibrary expects no change in the DOM. By using the class component with componentWillUnmount, someExternalLibrary.setup() and someExternalLibrary.cleanup() will work with the same DOM.
React doesn't expect you to remove nodes that React has created. Treat nodes created by React as read-only. Instead you should:
wrap the existing HTML inside another DIV element
However you don't explain why that isn't feasible:
but unfortunately, that's not always possible in our project, so I need another solution.
Without that info, which is your real issue, a solution cannot be given.
Although there are many posts on this question, none of them really helped me. I am starting to learn React and have a very simple code that isn't working for some reason.
The error happens when I am trying to call the this.setState() function inside of a react component that is inserted into an array. I imagined that this could be happening because the component is inside of an array, but I render it afterwards, so it doesn't make a lot of sense for me for that error to be thrown.
I am just trying to change the color of a div when the mouse is over it, by changing its state, which would change the class from "monsterEntry row" to "monsterEntry row toggleGrey". I have a simple css with
.toggleGrey{
background-color:grey;
}
Also, I have used create-react-app to start working on this. I appreciate any help I can get!
Here is my code:
Bestiary.js
import React from 'react';
class Bestiary extends React.Component{
constructor(props){
super(props);
this.state={
mouseIn: false //the state I want to change.
}
this.monsterlist = Object.values(props)[0];
this.bestiarycomponents = [];
this.monsterlist.forEach((value, index)=>{
this.bestiarycomponents.push((
<div key={index} className={this.state.mouseIn ? "monsterEntry row toggleGrey" : "monsterEntry row"} onMouseOver={this.setState({mouseIn: true})}> //Here is my problem.
<span className='monsterEntry-name col-6'>{value.name}</span>
<span className='monsterEntry-cr col-2'>{cr}</span>
<span className='monsterEntry-source col-4'>{value.source}</span>
</div>
))
});
}
render(){
return(
<div className='bestiary-container'>
<div className="bestiary-label row">
<div className='col-6'>Creature</div>
<div className='col-2'>CR</div>
<div className='col-4'>Sourcebook</div>
</div>
<div className="bestiary">
{this.bestiarycomponents} //here is where I render it.
<div><h2>Entries: {this.bestiarycomponents.length}</h2></div>
</div>
</div>
)
}
}
export default Bestiary;
I imagine the problem is in my Bestiary.js, but I'll also provide the index.js just in case.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Bestiary from './bestiary';
let httprequest = new XMLHttpRequest();
httprequest.responseType = 'json';
httprequest.onreadystatechange = ()=>{
if(httprequest.readyState === httprequest.DONE){
if(httprequest.status === 200) {
ReactDOM.render(<Bestiary bestiary={httprequest.response.monster}/>, document.getElementById('root'));
}
}
}
httprequest.open("GET", 'a json api', true)
httprequest.send();
The error gets thrown and the color does not change, also, but the rest works fine. The divs and everything else get rendered normally and I have also inspected the page and everything seems to be in order.
What I have tried:
Taking the render in the index.js out of a callback, and just putting some example object in place of the json response. Didn't work.
Using this.state={mouseIn : true} directly. Didn't work also.
Redoing everything, reinstalling node, none of that worked.
I have been searching for solutions for a long time now, I really appreciate any help I can get!!
Change
onMouseOver={this.setState({mouseIn: true})}
To
onMouseOver={() => this.setState({mouseIn: true})}
The way you wrote it it is being called right away vs. the correct way is to pass setState to a function.
I am basically trying to use the event listeners and add an event for the 'message' event which gets external events when this app is a webview in some mobile app.
The problem is that the react component DOES NOT update when the event comes in. I can identifiy that the event does come into my event listener function however, literally the react component DOES NOT update even if i do setState.
Here's my code:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
var showIfEventHappenedAndReactUpdated = false;
class App extends Component {
componentDidMount() {
//event listener to get events from external views
//when this app is used as a webview for example
document.addEventListener('message', function(e){
//I can see the event coming through here
//However, the react component itself doesn't update
showIfEventHappenedAndReactUpdated = true;
//I have even tried putting a this.setState({}) here but it doesn't work...as it doesn't update the react-component
})
}
render() {
if(showIfEventHappenedAndReactUpdated){
return (
<div className="App">
<p>Event Occured and React Updated</p>
</div>
);
}else{
return (
<div className="App">
<p>Event DID NOT OCCUR YET</p>
</div>
);
}
}
}
export default App;
BUT if I get the 'p' tag internally like this
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
componentDidMount() {
//event listener to get events from external views
//when this app is used as a webview for example
document.addEventListener('message', function(e){
document.getElementsByTagName('p')[0].innerHTML = "EVENT OCCURED"
})
}
render() {
return (
<div className="App">
<p>Event HAS NOT Occured</p>
</div>
);
}
}
}
export default App;
THEN it works. The only line change was
document.getElementsByTagName('p')[0].innerHTML = "EVENT OCCURED"
However, I need to actually update the react view instead of changing the text inside a raw DOM element.
What do i need to do in order to achieve that?
Aren't you getting an error if you try to setState in the event handler? addEventListener will set your this to the target the handler was bound to, which should be document in your case. Unless your document somehow has a setState method, this should be throwing an error.
Simply using an arrow function should fix it, or explicitly binding your handler to the current this when you are adding it as event listener.
If you are not getting an error and binding your function is not helping, try to console log this.setState just before adding event listener and inside the event listener and check if they are the same function. Alternately, try to add a callback to your setState to check if it's called.
Your callback function's this is not bound to the react component, thus setState is undefined. console.log(this) inside your callback to see.
To fix it, either bind the callback's this (.bind(this)) or use an arrow function if you have babel and whatnot set up.
You've got 2 problems in your code as it is at the time of writing this:
Your function in the event listener is not bound to your react element. As others have said, using the arrow syntax will fix this.
You're updating a non state variable. React will only render your component under 3 conditions: it's passed new props, you call setState() (with a new state, and you have to have returned a state in your constructor), or you call this.forceUpdate(). Changing a global variable will not cause a render.
First, showIfEventHappenedAndReactUpdated isn't part of the component's state so when you update it, the component isn't aware that it needs to update. In your component add a constructor method something like this:
constructor(props){
super(props);
this.state = { showIfEventHappenedAndReactUpdated: false };
}
Then update the value with this.setState({showIfEventHappenedAndReactUpdated : true}). This will make the component render again, and it will use the new value.
Second, the callback function isn't bound to the component's scope, and creates a new scope when it's called. Either use .bind(this) on the function call like this:
const callback = function(e){
//do stuff
}
document.addEventListener('message', callback.bind(this));
Or you can use an arrow function, which doesn't create its own scope like this:
document.addEventListener('message', (e)=>{ //do stuff };
The docs for React state that component functions can be accessed by a parent component via refs. See: https://facebook.github.io/react/tips/expose-component-functions.html
I am attempting to use this in my application but run into an "undefined is not a function" error when the child function is called. I'm wondering if this has anything to do with using the ES6 format for React classes because I don't see any other differences between my code and the docs.
I have a Dialog component that looks like the following pseudocode. The Dialog has a "Save" button that calls save(), which needs to call the save() function in the child Content component. The Content component collects information from child form fields and performs the save.
class MyDialog extends React.Component {
save() {
this.refs.content.save(); <-- save() is undefined
}
render() {
return (
<Dialog action={this.save.bind(this)}>
<Content ref="content"/>
</Dialog>);
}
}
class Content extends React.Component {
save() {
// Get values from child fields
// and save the content
}
}
I could instead pass a prop (saveOnNextUpdate) down to Content and then execute save whenever it is true, but I would rather figure out how to get the method detailed in the React doc above to work.
Any ideas on how to get the doc approach to work or access the child component function in a different way?
Redux connect accepts an option parametre as the forth parameter. In this option parameter you can set the flag withRef to true. Then you can access functions to refs by using getWrappedInstance(). Like this:
class MyDialog extends React.Component {
save() {
this.refs.content.getWrappedInstance().save();
}
render() {
return (
<Dialog action={this.save.bind(this)}>
<Content ref="content"/>
</Dialog>);
}
}
class Content extends React.Component {
save() { ... }
}
function mapStateToProps(state) { ... }
module.exports = connect(mapStateToProps, null, null, { withRef: true })(Content);
Read more about it here: https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options
Worth reading this article about use of refs and consider if there's better approaches: https://facebook.github.io/react/docs/refs-and-the-dom.html#dont-overuse-refs
An alternative way to do this would be to use some other prop name (other than ref). I've found that this also works well if you're using a library like styled-components or emotion For example in a connected MyComponent:
<MyComponent
...
innerRef={(node) => { this.myRef = node; }}
/>
As it turns out, m90 was right -- this was a different issue entirely. I'm posting the solution in case someone runs into the same problem in the future.
My application is built with Redux, and the problem stems from using the react-redux connect function to connect a component to the store/global state. For some reason, exporting a component and connecting it to the store makes it impossible to access the functions inside of it. In order to get around this, I had to remove all use of global state from Content so that I could export it as a "dumb" component.
To be more clear, Content.js looked like this:
var connect = require('react-redux').connect;
class Content extends React.Component {
save() {
// Get values from child fields
// and save the content
// Use of this.props.stateObject
}
}
function mapStateToProps(state) {
const {
stateObject
} = state;
return {
stateObject
};
}
module.exports = connect(mapStateToProps)(Content);
Removing the use of global state (and therefore the use of connect and mapStateToProps allowed me to export the component using:
module.exports = Content;
Accessing this.refs.content.save() magically worked after doing this.
I have a React component like this:
import React, { Component, PropTypes } from 'react';
import SubnavActions from '../../actions/subnav-actions';
import FeedActions from '../../actions/feed-actions';
export default class SubnavItemModule extends Component {
render() {
return (
<div className={'item ' + this.props.active} thumbImg={this.props.thumbImg} _text={this.props._text} _id={this.props._id} onClick={this.handler}>
<p>{this.props._text}</p>
</div>
);
}
handler() {
console.log('subnav-filter-item-module handler launching');
}
};
onClick{this.handler} used to work before, but now it launches onClick event everytime the component is rendered.
If I click it though, nothing happens.
In other demo I used onClick={ e => this.handler(e)} succesfully, but in this work nothing happens.
I started using Babel 6 and Babelify before this occured, but I can't really understand here what is causing and what. Earlier I used Reactify. So my questions are the following:
What is the proper way to launch the onClick event here?
Why is it launched on render?
Is this related to Babelify/Reactify? If so, how?
EDITS:
Corrected the original code sample.
Here is the parent module:
http://pastebin.com/rSKf4wR8
If you wonder why am I writing that differently (using import instead of require), I am in the middle of changing the syntax. If you ask why, well, I don't know. I just try to follow some tutorials and see how others write. I don't really even know the difference between require and import.
Also, I just realised something. Can this be caused because I am not passing any function as a prop in the parent?
Somebody is having similar issue with Rails app, does this provide any more info: React rails app not executing onClick handler when props are used in a conditional expression
In your case you call function, but you need pass to onClick reference to function
onClick={ this.handler }
Example
if you need pass some argument to handler you can use .bind, like this
onClick={ this.handler.bind(this, 100) }
handler(x) {}
Example
or with arrow function
onClick={ () => this.handler(100) }