ReactJs - Why wont child event change state of parent? - javascript

I have a created a menu component in reactjs. As you can see, the parent component has a method called "handleClick which toggles the "open state" of the menu, opening and closing it accordingly.
Now, I am trying to pass a click event from the child component "MenuItem" (which is a link in the menu) to the parent "Menu" component so that when one of the menu items is clicked the menu closes.
I have tried to do this in a number of ways. At the moment, I have bound the click event of each "MenuItem" in "MenuList" (the list of MenuItems) to a prop called "whenClicked" and then bound "whenClicked" to the "handleClick" method of "Menu".
The problem is that this seems to have no effect on "Menu". Neither the React tool in Chrome, nor the regular dev console are giving me any errors but the menu does not close when I click one of the MenuItems. The React tool in Chrome allows me to view the virtual DOM and I can see that all of the onClick functions are defined.
Below is the code. As you can see, I am using the same methodology to pass a click event from a different component ("MenuToggle") to "Menu". Oddly enough this works fine and clicking on the toggle button changes the state of "Menu" successfully and opens and closes the menu. I am using the "react-scroll" module for "MenuItem" so maybe that is the issue. Any light anyone can shed on this would be helpful and I would love to know what I am doing incorrectly!
var Menu = React.createClass({
getInitialState: function() {
return {open: false, mobi: false}
},
handleClick: function() {
this.setState({open: !this.state.open})
},
closeOnMobiScroll: function() {
/*
if(this.state.mobi === false) {
this.setState({open: false})
}
*/
},
updateDimensions: function() {
$(window).width() >= 767 ? this.setState({mobi: true}) : this.setState({mobi: false});
},
componentWillMount: function() {
this.updateDimensions();
},
componentDidMount: function() {
$(window).on("resize", this.updateDimensions);
},
componentWillUnmount: function() {
$(window).on("resize", this.updateDimensions);
},
render: function() {
return (
<div id="menu" className={(this.state.open ? 'open' : '')} >
<div id="menu-inner-wrap">
<MenuTitle />
<MenuToggle whenClicked={this.handleClick}/>
<MenuList whenClicked={this.handleClick}/>
</div>
</div>
)
}
});
module.exports = Menu;
var MenuItem = React.createClass({
render: function() {
return (
<li className="menu-link">
<Link to={this.props.menuLink} spy={true} smooth={true} duration={500}>
<i className={this.props.icon}></i>
<span className="menu-link-text">{this.props.menuTitle}</span>
</Link>
</li>
);
}
});
var MenuList = React.createClass({
getInitialState: function() {
return {data: []}
},
componentWillMount: function() {
$.ajax({
url: 'http://10.0.0.97:8888/public-code/React.cv/data/data.json',
dataType: 'json',
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, error) {
var err = JSON.parse(xhr.responseText);
console.log(err.Message);
}
});
},
render: function() {
var list = this.state.data.map(function(menuItemProps) {
return <MenuItem onClick={this.props.whenClicked} {...menuItemProps} key={menuItemProps.id} />
}.bind(this));
return (
<ul id="menu-list">
{list}
</ul>
)
}
});

It seems like you still need to bind the onClick to something that the DOM will handle. Adding an onClick attribute to MenuItem allows you to have a prop inside MenuItem, but you still need to bind it:
var MenuItem = React.createClass({
render: function() {
return (
<li className="menu-link" onClick={this.props.onClick}>
<Link to={this.props.menuLink} spy={true} smooth={true} duration={500}>
<i className={this.props.icon}></i>
<span className="menu-link-text">{this.props.menuTitle}</span>
</Link>
</li>
);
}
});
}
In the above example, onClick is added to the li:
<li className="menu-link" onClick={this.props.onClick}>
The best example in the documentation of behaviour like this is in Expose Component Functions.
In that example you can see that the Todo child component finally binds to div and then bubbles up in a similar way:
var Todo = React.createClass({
render: function() {
return <div onClick={this.props.onClick}>{this.props.title}</div>;
},
//this component will be accessed by the parent through the `ref` attribute
animate: function() {
console.log('Pretend %s is animating', this.props.title);
}
});

Related

onMouseLeave event is not triggering

This is nav component. This nav component rendered at run time. Text should be visible when mouse will enter to div with ref "text_nav" and text should hide when mouse will leave div with ref "text_nav".
onMouseLeave is not working
var React = require('React');
var $ = require('jquery');
var Nav = React.createClass({
getInitialState: function() {
return {
items: []
}
},
componentWillMount: function() {
var _this = this;
this.serverRequest =
$.post("/nav", {}, function(result) {
_this.setState({
items: result.data
});
})
},
onMouseEnter: function() {
this.refs.text_navigator.style = {display: true}
},
onMouseLeave: function() {
this.refs.text_navigator.style = {display: 'none'}
},
render: function() {
var text = this.state.items.map(function(data, index) {
var icon = "text_" + data.sname;
return (
<div id={icon} key={index} className="text_nav_item">
<p>
<span><a href={data.url}>{data.title} </a></span>
</p>
</div>
);
});
return (
<div id="nav" className="fixed" style={{zIndex: 1018}} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<div id="text_nav" ref="text_navigator" style={{display: 'none'}} >
<div id="text_nav_content">
{item_text}
</div>
</div>
</div>
)
}
})
First off, the ref is text_nav in your render method, not text_navigator as you're using in onMouseEnter and onMouseLeave. But the main issue is that you can't set the display style the way you're trying to do, as
this.refs.text_navigator.style = {display: true}
The most common approach to take is to set a boolean in state, perhaps called displayTextNavigator. In getInitialState, set it to false, and then your onMouseEnter and onMouseLeave functions can be:
onMouseEnter: function() {
this.setState({ displayTextNavigator: true})
},
onMouseLeave: function() {
this.setState({ displayTextNavigator: false})
},
Now in your render method, you can change your wrapping div to look like this:
<div id="text_nav" ref="text_nav" style={{display: this.state.displayTextNavigator ? 'block': 'none'}} >
You can pull that ternary operator out to earlier in the render method if you'd like it be more readable.

ReactJS IE9 state not being refreshed

I have an application that, in IE9, does not seem to render properly on state change. Pretty much it is just a product grid that when you click on an object, it should pop open a modal box with some product information and the ability to add the item to your shopping cart. Right now, the grid renders, and when an object gets clicked only the modal overlay gets shown but the modal content is never loaded. This works in all other browsers. In addition, this works when IE9 dev console is open.
Here's pretty much how we are doing this:
var Modal = React.createClass({
propTypes: {
modalOpen: React.PropTypes.bool.isRequired,
children: React.PropTypes.element.isRequired,
onClose: React.PropTypes.func
},
render: function() {
if (this.props.modalOpen) {
return (
<div>
<div className="modal-overlay" onClick={this.props.onClose} />
<div className="modal-content">
{this.props.children}
</div>
</div>
)
}
else {
return (<div />)
}
}
});
var ProductModal = React.createClass({
propTypes: {
modalOpen: React.PropTypes.bool.isRequired,
onClose: React.PropTypes.func
},
render: function() {
return(
<Modal modalOpen={this.props.modalOpen} onClose={this.props.onClose}>
<div className="pdModal">
This is where product details would go if we had them...
</div>
</Modal>
);
}
});
var ProductGrid = React.createClass({
getInitialState: function() {
return {
modalIsOpen: false
}
},
openModal: function() {
this.setState({
modalIsOpen: true
});
},
closeModal: function() {
this.setState({
modalIsOpen: false
});
},
render: function() {
return (
<div>
<div onClick={this.openModal}>CLICK ME TO OPEN THE MODAL</div>
<ProductModal modalOpen={this.state.modalIsOpen} onClose={this.closeModal} />
</div>
);
}
});
ReactDOM.render(
<ProductGrid/>,
document.getElementById('container')
);
Fiddle available here. Yes I know the fiddle works on IE9, which is weirder because this is exactly how this is implemented on our site. The only difference is that we are making an ajax request and waiting for results before rendering.
Any thoughts?

React.JS this.state is undefined

I currently have this component in React.JS which shows all the Images passed to it in an array and onMouseOver it shows a button below. I planed on using setState to check the variable hover if is true or false and toggle the button of that image accordingly however I keep getting the following error:
Uncaught TypeError: Cannot read property 'state' of undefined
var ImageList = React.createClass({
getInitialState: function () {
return this.state = { hover: false };
},
getComponent: function(index){
console.log(index);
if (confirm('Are you sure you want to delete this image?')) {
// Save it!
} else {
// Do nothing!
}
},
mouseOver: function () {
this.setState({hover: true});
console.log(1);
},
mouseOut: function () {
this.setState({hover: false});
console.log(2);
},
render: function() {
var results = this.props.data,
that = this;
return (
<ul className="small-block-grid-2 large-block-grid-4">
{results.map(function(result) {
return(
<li key={result.id} onMouseOver={that.mouseOver} onMouseOut={that.mouseOut} ><img className="th" alt="Embedded Image" src={"data:" + result.type + ";" + "base64," + result.image} /> <button onClick={that.getComponent.bind(that, result.patientproblemimageid)} className={(this.state.hover) ? 'button round button-center btshow' : 'button round button-center bthide'}>Delete Image</button></li>
)
})}
</ul>
);
}
});
You get the error because you're storing the reference to this in a that variable which you're using to reference your event handlers, but you're NOT using it in the ternary expression to determine the className for the button element.
your code:
<button
onClick={ that.getComponent.bind(that, result.patientproblemimageid) }
className={ (this.state.hover) ? // this should be that
'button round button-center btshow' :
'button round button-center bthide'}>Delete Image
</button>
When you change this.state.hover to that.state.hover you won't get the error.
On a side note, instead of storing the reference to this in a that variable you can simple pass a context parameter to the map() method.
results.map(function (result) {
//
}, this);
In ES5 format you cannot set this.state directly
var ImageList = React.createClass({
getInitialState: function () {
return { hover: false };
},
render : function(){
return(<p>...</p>);
});
However with new ES6 syntax you can essentially manage this:
class ImageList extends React.Component{
constructor (props) {
super(props);
this.state = {hover : false};
}
render (){ ... }
}

How to set state and props on a single click event in reactjs

I have the following react component which will be used to toggle a menu:
var MenuToggle = React.createClass({
render: function() {
return(
<a id="menu-toggle" href="javascript:void(0);">
<i id="closed" className="fa fa-bars"></i>
<i id="open" className="fa fa-times"></i>
</a>
)
}
});
When this component is clicked, I need to toggle it's state so the 'hamburger' icon becomes an 'x' icon. I also need to add a prop to the component so that the parent menu's state can be updated by listening for 'MenuToggle' component to receive the prop. Since I need to do both based on the same click event, I have tried:
var MenuToggle = React.createClass({
getInitialState: function() {
return {open: false}
},
handleClick: function() {
var that = this;
function setTheState() {
that.setState({open: !that.state.open})
}
function setTheProps() {
that.props.whenClicked
}
setTheState()
setTheProps()
},
render: function() {
return(
<a id="menu-toggle" href="javascript:void(0);" onClick={this.handleClick} className={(this.state.open ? 'open' : '')}>
<i id="closed" className="fa fa-bars"></i>
<i id="open" className="fa fa-times"></i>
</a>
)
}
});
Basically, I am calling the single function 'handleClick' which should set / toggle the state of 'MenuToggle', and add the 'whenClicked' prop.
The result is that the state is toggled but 'whenClicked' is not set. whenClicked needs to be set because the parent component, 'Menu' listens for 'whenClicked' in 'menuToggle'. When 'Menu' hears 'whenClicked', it fires 'handleClick' which toggles it's own state:
var Menu = React.createClass({
getInitialState: function() {
return {open: false, mobi: false}
},
handleClick: function() {
this.setState({open: !this.state.open})
},
closeOnMobiScroll: function() {
/*
if(this.state.mobi === false) {
this.setState({open: false})
}
*/
},
updateDimensions: function() {
$(window).width() >= 767 ? this.setState({mobi: true}) : this.setState({mobi: false});
},
componentWillMount: function() {
this.updateDimensions();
},
componentDidMount: function() {
$(window).on("resize", this.updateDimensions);
},
componentWillUnmount: function() {
$(window).on("resize", this.updateDimensions);
},
render: function() {
return (
<div id="menu-wrap">
<MenuToggle whenClicked={this.handleClick} />
<div id="menu" className={(this.state.open ? 'open' : '')} >
<MenuTitle />
<MenuList whenClicked={this.handleClick}/>
</div>
</div>
)
}
});
Is there a way to toggle the state and set the 'whenClicked' prop with one click event?
let me see if I can understand this, when clicked:
toggle component's state
pass the trigger to parent through props
if the above assumption is true, then here's what I am suggesting:
as for convention, use "on" and "handle" as prefix
classnames could be a good tool to learn since fb is also using it
since set state is async, you can use its call back function to trigger things when the state is set.
pass the new state to its parent is a good practice to let the parent know the child's state
here's the modified version:
var MenuToggle = React.createClass({
getInitialState: function() {
return {
isOpen: false
};
},
_handleClick: function(e) {
e.preventDefault();
this.setState({isOpen: !this.state.isOpen}, function() {
this.props.onClick(this.state.isOpen);
});
},
render: function() {
var iconClass = classNames('fa', this.state.isOpen ? 'fa-times' : 'fa-bars');
return(
<a id="menu-toggle" onClick={this._handleClick}>
<i className={iconClass}></i>
</a>
)
}
});
hope this helps =)
var MenuToggle = React.createClass({
getInitialState: function() {
return {
isOpen: false
};
},
_handleClick: function(e) {
e.preventDefault();
this.setState({isOpen: !this.state.isOpen}, function() {
this.props.onClick(this.state.isOpen);
});
},
render: function() {
var iconClass = classNames('fa', this.state.isOpen ? 'fa-times' : 'fa-bars');
return(
<a id="menu-toggle" onClick={this._handleClick}>
<i className={iconClass}></i>
</a>
)
}
});

How to call another component from onClick function in ReactJS

I am learning Reactjs. I have implemented one sample react app with rails. I have search a lots to find the solution but I didn't find any. I wanted to call another component from onClick function. But nothing happen. Is that possible what I try to achieve? If yes, then please point me where I do mistake and If not, then which way I can implement. Here is my code:
var Comment = React.createClass({
render: function () {
return (
<div id={"comment_"+ this.props.id }>
<h4>{ this.props.author } said:</h4>
<p>{ this.props.desc }</p>
<a href='' onClick={this.handleDelete}>Delete</a> | #this is for delete which works great
<a href='' onClick={this.handleEdit}>Edit</a>
# If I put here then it works fine <UpdateComments comments={ this.props} /> but I don't want it here
</div>
)
},
handleDelete: function(event) {
$.ajax({
url: '/comments/'+ this.props.id,
type: "DELETE",
dataType: "json",
success: function (data) {
this.setState({ comments: data });
}.bind(this)
});
},
handleEdit: function(event) {
var Temp = React.createClass({
render: function(event){
return(<div>
<UpdateComments comments={ this.props} /> #here I want to call UpdateComments component
</div>
)
}
});
}
});
Update:
If I try below trick then it call the component but reload the page and again disappear called component :(
handleEdit: function() {
React.render(<UpdateComments comments={ this.props} /> , document.getElementById('comment_'+ this.props.id));
}
any other detail if you required then feel free to ask. Thanks in advance. :)
Maybe this fiddle could point you in right way
var Hello = React.createClass({
render: function() {
return <div>Hello {this.props.name}
<First1/>
<First2/>
</div>;
}
});
var First1 = React.createClass({
myClick: function(){
alert('Show 1');
changeFirst();
},
render: function() {
return <a onClick={this.myClick}> Saludo</a>;
}
});
var First2 = React.createClass({
getInitialState: function(){
return {myState: ''};
},
componentDidMount: function() {
var me = this;
window.changeFirst = function() {
me.setState({'myState': 'Hey!!!'})
}
},
componentWillUnmount: function() {
window.changeFirst = null;
},
render: function() {
return <span> Saludo {this.state.myState}</span>;
}
});
React.render(<Hello name="World" />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.1/react-with-addons.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.1/JSXTransformer.js"></script>
<script src="https://facebook.github.io/react/js/jsfiddle-integration.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
Basically I use those links:
communicate between components
dom event listeners
It hopes this helps.
Also, you could use the container component and use it like a bridge between both components.

Categories

Resources