React: Unable to access child props in parent's event handler - javascript

I'm using React to create a UI and I have a parent component and a child component, something along these lines:
// Child component
var ListItem = React.createClass({
render: function() {
var link_details = (
<div>
Start Date: {this.props.my_data.start_date}<br/>
End Date: {this.props.my_data.end_date}<br/>
</div>
);
return (
<li>
<a onClick={this.props.clickHandler}>
{ this.props.my_data.name }
</a>
{link_details}
</li>
)
}
});
// Parent component
var Sidebar = React.createClass({
getInitialState: function() {
return {
my_data: [],
};
},
handleListItemClick: function(e){
console.log(e.target);
console.log(e.target.props);
},
render: function() {
var myLinks = this.state.my_data.map(function(mylink) {
return (
<ListItem key={mylink.id} my_data={mylink} clickHandler={this.handleListItemClick} />
);
}.bind(this));
return (
<div>
<ul className="nav nav-sidebar">
{ myLinks }
</ul>
</div>)
}
});
I want the click event on the child to trigger the parent's handler so that the parent can update its state based on what was clicked in the child. While the code I have above works, and the parent's handler is called, I am unable to access any of the child component's props. I'm not sure if that's by design and I should pass data from the child to the parent in a different way, or if I'm doing something wrong. I'm still very new to React, so any advice is appreciated. Thanks!

You can not do that but you can pass data from child to parent via callback
<li>
<a onClick={this.props.clickHandler.bind(null,this.props.my_data.name)}>
{ this.props.my_data.name }
</a>
{link_details}
</li>
or using arrow function if you are using es6
<li>
<a onClick={() => this.props.clickHandler(this.props.my_data.name)}>
{ this.props.my_data.name }
</a>
{link_details}
</li>
Edit
Why passing null?
Things to remember:
Automatic binding methods to 'this' happens when your component mounts.
There are two conditions
1.Calling a callback passed from parent component to a child component
When we directly pass functions (e.g. this.clickHandler) to a child component without worrying about the value of 'this' when the function is actually called.
React then the replaces the standard Function.prototype.bind method with its own function to help stop you from doing anything silly (like trying to change the already-bound value of 'this'), so you instead have to pass 'null' to say "I understand this will only alter the arguments".
2.Calling a function defined within same component
React does not do this for function calls within the same component
Rules for binding
If you want to set the first argument by calling .bind on a function...
passed in via props, pass null as the first argument e.g.
this.props.funcName.bind(null, "args")
taken from 'this', pass 'this' as the first argument e.g.
this.funcName.bind(this, "args")

You can do so:
var ListItem = React.createClass({
clickItem: function (e) {
this.props.clickHandler(e, this.props.my_data); // now you can pass any data to parent
},
render: function() {
var link_details = (
<div>
Start Date: {this.props.my_data.start_date}<br/>
End Date: {this.props.my_data.end_date}<br/>
</div>
);
return (
<li>
<a onClick={this.clickItem}>
{ this.props.my_data.name }
</a>
{link_details}
</li>
)
}
});

I took a look at the answer on Pass props to parent component in React.js and came up with the following:
// Parent component
var Sidebar = React.createClass({
getInitialState: function() {
return {
my_data: [],
};
},
handleListItemClick: function(data_passed, e){
console.log(data_passed);
},
render: function() {
var myLinks = this.state.my_data.map(function(mylink) {
return (
<ListItem key={mylink.id} my_data={mylink} clickHandler={this.handleListItemClick.bind(null, mylink.id)} />
);
}.bind(this));
return (
<div>
<ul className="nav nav-sidebar">
{ myLinks }
</ul>
</div>)
}
});
This does seem to work- I'd be interested in seeing other solutions and which one is the "best" and why.

Related

How to call a React component function from inside a javascript function?

I am in an event function and I would like to create a new alert popup (I am using the react-portal library):
onNewAlert: function(username) {
var divModal = (
<Portal ref={'Portal'+username}>
<div id={'div'+username}>
<br /><br/ >Alert for {username}
</div>
</Portal>);
...
}
But then I would have to call a function that is inside a Portal. I could normally do this with references if I was in the render() function, but I am in an event.
this.refs['Portal'+username].openPortal(); // openPortal is a function of the Portal component
Is there a way to call a component function for a component created on the fly in a javascript function?
Even if you could call portal.openPortal() it wouldn't do anything since the component created in the event handler wouldn't be attached to the DOM.
Instead of trying to render the Portal in the event handler function, the event handler should change the component state which will trigger render().
onNewAlert: function(username) {
this.setState({ showAlert: true });
}
The render() function would then use the state variable for the Portal component's isOpened property:
render: function () {
return (
<div>
...
<Portal isOpened={this.state.showAlert}>
...
</Portal>
</div>
);
}

how to control child elements by onClick from parent in reactjs?

crafting basic app in react as following:
parent container receive state by ajax and contains four columns, left column - all messages items, second column message body (should be shown when message element is clicked as well as controls), next - message controls (next, prev) and action type:
how to to properly attach a controls to children elements for instance for onClick to message element? here is the snippet of a parent:
var ModerationContainer = React.createClass({
getInitialState: function () {
return {data: []};
},
componentDidMount: function () {
...
},
LoadMessagesFromApi: function () {
jQuery.ajax({
... // loads messages from json api into state
});
},
testor: function () {
alert();
},
render: function () {
var allMessageItems = this.state.data.map(function (message) {
return (
<MessageItem id={message.id} key={message.id} onClick={this.testor}/>
);
}, this);
return (
<div>
<div className="col-md-2 messageColumn">
{allMessageItems}
</div>
<MessageBodyColumn/>
<ControlsColumn />
<BlockColumn />
</div>
);
}
});
No onclick event is executed after i click message block althrought I attached this to map while rendering messages block, what did i wrong ?
Also, how it is possible to auto select first message item if none of them clicked ?
Any hints or links on tutorials from experienced with react people much appreciated
I would right the MessageItem on the parent like
<MessageItem key={message.id} onClick={this.testor.bind(this, message.id) }/>
Then inside your MessageItem component you can take the onClick handler from the pros, lets say MessageItem is a div your render function could be like
render()
{
const onClick = this.props.onClick;
const label = `Message${this.props.key}`;
return( <div onClick={ onClick }> { label }</div>)
}
and if you write your testor like
testor: function ( id ) {
alert( id );
}
You can see the id of the clicked message.

Get wrapper DOM element attributes from a method in React

I have a link in a React component:
<a href="#goals-tab" className={ this.setTabStyle()}>Goals</a>
Now, inside setTabStyle method, can I access attributes of the a element, like href without explicitly passing it to the method as a parameter?
If you use a ref, then your component renders DOM without the styles, and then applies the new styles. So the user will notice the change of styles.
I would advise to pass link as a parameter to setTabStyle(link), or make the link another prop of your component:
var Component = React.createClass({
handleClick: function (e) {
console.log(e.currentTarget.getAttribute('href'));
},
setTabStyle: function () {
if (this.props.link == this.props.activelink) {
return myActiveLinkStyle
} else {
return myInactiveLinkStyle
}
},
render: function() {
return <a href={this.props.link} style={this.setTabStyle()} onClick={this.handleClick}>Click</a>;
}
});
That way, you get the right style from the initial load..

How to append to dom in React?

Here's a js fiddle showing the question in action.
In the render function of a component, I render a div with a class .blah. In the componentDidMount function of the same component, I was expecting to be able to select the class .blah and append to it like this (since the component had mounted)
$('.blah').append("<h2>Appended to Blah</h2>");
However, the appended content does not show up. I also tried (shown also in the fiddle) to append in the same way but from a parent component into a subcomponent, with the same result, and also from the subcomponent into the space of the parent component with the same result. My logic for attempting the latter was that one could be more sure that the dom element had been rendered.
At the same time, I was able (in the componentDidMount function) to getDOMNode and append to that
var domnode = this.getDOMNode();
$(domnode).append("<h2>Yeah!</h2>")
yet reasons to do with CSS styling I wished to be able to append to a div with a class that I know. Also, since according to the docs getDOMNode is deprecated, and it's not possible to use the replacement to getDOMNode to do the same thing
var reactfindDomNode = React.findDOMNode();
$(reactfindDomNode).append("<h2>doesn't work :(</h2>");
I don't think getDOMNode or findDOMNode is the correct way to do what I'm trying to do.
Question: Is it possible to append to a specific id or class in React? What approach should I use to accomplish what I'm trying to do (getDOMNode even though it's deprecated?)
var Hello = React.createClass({
componentDidMount: function(){
$('.blah').append("<h2>Appended to Blah</h2>");
$('.pokey').append("<h2>Can I append into sub component?</h2>");
var domnode = this.getDOMNode();
$(domnode).append("<h2>appended to domnode but it's actually deprecated so what do I use instead?</h2>")
var reactfindDomNode = React.findDOMNode();
$(reactfindDomNode).append("<h2>can't append to reactfindDomNode</h2>");
},
render: function() {
return (
<div class='blah'>Hi, why is the h2 not being appended here?
<SubComponent/>
</div>
)
}
});
var SubComponent = React.createClass({
componentDidMount: function(){
$('.blah').append("<h2>append to div in parent?</h2>");
},
render: function(){
return(
<div class='pokey'> Hi from Pokey, the h2 from Parent component is not appended here either?
</div>
)
}
})
React.render(<Hello name="World" />, document.getElementById('container'));
In JSX, you have to use className, not class. The console should show a warning about this.
Fixed example: https://jsfiddle.net/69z2wepo/9974/
You are using React.findDOMNode incorrectly. You have to pass a React component to it, e.g.
var node = React.findDOMNode(this);
would return the DOM node of the component itself.
However, as already mentioned, you really should avoid mutating the DOM outside React. The whole point is to describe the UI once based on the state and the props of the component. Then change the state or props to rerender the component.
Avoid using jQuery inside react, as it becomes a bit of an antipattern. I do use it a bit myself, but only for lookups/reads that are too complicated or near impossible with just react components.
Anyways, to solve your problem, can just leverage a state object:
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://fb.me/react-0.13.3.js"></script>
</head>
<body>
<div id='container'></div>
<script>
'use strict';
var Hello = React.createClass({
displayName: 'Hello',
componentDidMount: function componentDidMount() {
this.setState({
blah: ['Append to blah'],
pokey: ['pokey from parent']
});
},
getInitialState: function () {
return {
blah: [],
pokey: []
};
},
appendBlah: function appendBlah(blah) {
var blahs = this.state.blah;
blahs.push(blah);
this.setState({ blah: blahs });
},
render: function render() {
var blahs = this.state.blah.map(function (b) {
return '<h2>' + b + '</h2>';
}).join('');
return React.createElement(
'div',
{ 'class': 'blah' },
{ blahs: blahs },
React.createElement(SubComponent, { pokeys: this.state.pokey, parent: this })
);
}
});
var SubComponent = React.createClass({
displayName: 'SubComponent',
componentDidMount: function componentDidMount() {
this.props.parent.appendBlah('append to div in parent?');
},
render: function render() {
var pokeys = this.props.pokeys.map(function (p) {
return '<h2>' + p + '</h2>';
}).join('');
return React.createElement(
'div',
{ 'class': 'pokey' },
{ pokeys: pokeys }
);
}
});
React.render(React.createElement(Hello, { name: 'World' }), document.getElementById('container'));
</script>
</body>
</html>
Sorry for JSX conversion, but was just easier for me to test without setting up grunt :).
Anyways, what i'm doing is leveraging the state property. When you call setState, render() is invoked again. I then leverage props to pass data down to the sub component.
Here's a version of your JSFiddle with the fewest changes I could make: JSFiddle
agmcleod's advice is right -- avoid JQuery. I would add, avoid JQuery thinking, which took me a while to figure out. In React, the render method should render what you want to see based on the state of the component. Don't manipulate the DOM after the fact, manipulate the state. When you change the state, the component will be re-rendered and you'll see the change.
Set the initial state (we haven't appended anything).
getInitialState: function () {
return {
appended: false
};
},
Change the state (we want to append)
componentDidMount: function () {
this.setState({
appended: true
});
// ...
}
Now the render function can show the extra text or not based on the state:
render: function () {
if (this.state.appended) {
appendedH2 = <h2>Appended to Blah</h2>;
} else {
appendedH2 = "";
}
return (
<div class='blah'>Hi, why isn't the h2 being appended here? {appendedH2}
<SubComponent appended={true}/> </div>
)
}

ReactJS Cortex Object updated by render not invoked?

I am trying to implement Emberjs's Todo app as a practice exercise for Cortex by mquan on github. I am currently implementing the "All", "Active", "Completed" filter where clicking an anchor will result in the anchor being highlighted (class added).
I created the following:
var filtercortex = new cortex([
{title:'all', selected:true, key:1},
{title:'completed', selected:false, key:2},
{title:'active', selected:false, key:3}
]);
With the following render function (in the parent):
render: function() {
var filters = filterCortex.map(function(filter) {
return (
<li>
<FilterAnchor cortex={filterCortex} filter={filter} />
</li>
)
});
...
return ...
<ul id='filters'>
{filters}
</ul>
And FilterAnchor's definition:
var FilterAnchor = React.createClass({
handleClick: function() {
var that = this;
this.props.cortex.forEach(function(filter) {
if (filter.key.getValue() == that.props.filter.key.getValue()) {
console.log(filter.title.getValue(), true);
filter.selected.set(true);
} else {
console.log(filter.title.getValue(), false);
filter.selected.set(false);
}
});
return false;
},
render: function() {
var className = (this.props.filter.selected.getValue()) ? 'selected' : '';
return (
<a className={className} href="#" onClick={this.handleClick}>
{this.props.filter.title.getValue()}
</a>
)
}
});
right now, I do not see the class 'selected' being applied to the anchor links when I am clicking.
However, upon investigation I notice this:
Clicking "All":
All true
Completed false
Active false
Clicking "Completed":
All true
Completed false
Active false
So I am certain that the objects inside filtercortex has been updated properly (you can open up firebug to check). However, FilterAnchor.render is not being triggered.
Is this a bug?
Source code: https://github.com/vicngtor/ReactTodo/blob/cortex/script.jsx
The sample at the top of the Cortex readme has this at the bottom:
orderCortex.on("update", function(updatedOrder) {
orderComponent.setProps({order: updatedOrder});
});
Is there an equivalent section in your code? If not, then the problem is that the update event for the cortex data store isn't set to trigger an update of the view, which is made through a call to setProps on the top level React component in this example.

Categories