React JS: Problems with passing my handler function with props - javascript

I have three classes. In the first, I declare a handler-function (handleItemClick). I want to pass this function to the last class, a clickable list-item.
First class:
module.exports = React.createClass({
...
renderList: function() {
return <SortableList topics={this.state.topics} whenItemClicked={this.handleItemClick} seminar={this.state.seminar} />
},
/* THE FUNCTION WHICH SHOULD BE INVOKED IN THE LIST ITEM */
handleItemClick: function(type, item) {
/** handle click **/
},
...
});
The second is the list, creating list items. This class should get the handler function from the first class and pass it to the third.
sortable-list.jsx:
var React = require('react');
var Sortable = require('sortablejs');
var SortableMixin = require('sortablejs/react-sortable-mixin');
var ListItem = require('./list-item');
module.exports = React.createClass({
mixins: [SortableMixin],
getInitialState: function() {
return {
items : this.props.topics
};
},
handleClick: function(type, item) {
// invoke whenItemClicked of the previous class
this.props.whenItemClicked(type, item);
},
handleSort: function (/** Event */evt) {
/**/
},
render: function() {
return <ul className="nav nav-pills nav-stacked mail-nav" style={{marginTop: '0px'}}>{
this.state.items.map(function (topic) {
return <ListItem whenItemClicked= {this.handleClick} type={"topic"} item={topic} key={topic.id} />
})
}</ul>
}
});
And finally the class list-item.jsx where clicks can appear.
var React = require('react');
module.exports = React.createClass({
handleClick: function() {
// this should invoke the handleClick of sortable-list.jsx
this.props.whenItemClicked(this.props.type, this.props.item);
},
render: function() {
return <li>
<a onClick={this.handleClick} className={this.props.selected ? "selected" : ""}>
{this.props.item.title}
</a>
</li>
}
});
When I click on a list item, the function handleClick in list-item.jsx is called. But the call of this.props.whenItemClicked fails (is undefined) for some reason.
When I debug the code, even in sortable-list.jsx in line return <ListItem whenItemClicked= {this.handleClick} type={"topic"} item={topic} key={topic.id} /> the handleClick is undefined.
I just can't figure out why.

In .map by default this refers to global scope (in browser it is window), so when you call this.handleClick in .map it will be undefined because in window there is not function handleClick, pass second argument to you .map that will set this in callback, like this
this.state.items.map(function (topic) {
return <ListItem whenItemClicked= {this.handleClick} type={"topic"} item={topic} key={topic.id} />
}, this)

Related

Cannot update during an existing state transition - Not using any illegal setState()

Some one Please Help me with this error
Cannot update during an existing state transition
When I am rendering this I'm getting error like below
Cannot update during an existing state transition (such as within
render or another component's constructor). Render methods should be
a pure function of props and state; constructor side-effects are an
anti-pattern, but can be moved to componentWillMount.
I have tried putting this.props.ifListChanged(this); this code inside the componentWillUpdate and componentDidUpadate but it is taking too much time but without errors(almost 2 mins).
import React from 'react';
import ListItemComponent from './ListItem.jsx';
import DropDownButtonComponent from './DropDownButton.jsx';
import DropDownStyle from '../../../../css/sass/drop-down.scss';
module.exports = React.createClass({
handleClick: function () {
this.setState({open: !this.state.open});
},
getInitialState: function () {
return {
open: false,
//listItems: this.props.listItems,
selectedItems:[],
title: this.props.dropdownTitle
}
},
handleItemClick: function (item) {
var selectedItems = [];
if(this.props.multiple == true){
selectedItems = this.state.selectedItems;
if(selectedItems.indexOf(item)==-1){
selectedItems.push(item);
}else{
selectedItems.splice(selectedItems.indexOf(item),1)
}
this.setState({
title: this.state.selectedItems.length+" selected",
selectedItems: selectedItems
});
} else{
selectedItems = [];
selectedItems.push(item);
this.setState({
title: item,
selectedItems: selectedItems,
open: false
});
}
//this.sendStateToParent();
},
sendStateToParent: function(){
this.props.ifListChanged(this);
},
handleTextChange: function (event) {
var filteredItems = [];
this.props.listItems.map(function(item){
if(item.toLowerCase().search(event.target.value.toLowerCase()) != -1){
filteredItems.push(item);
}
},this);
this.setState({
listItems: filteredItems
});
},
clearSelected: function(){
this.setState({
title: this.props.dropdownTitle,
selectedItems: [],
});
},
render: function () {
this.props.ifListChanged(this);
var index = 0;
var list=[];
if (this.state.listItems != undefined) {
list = this.state.listItems.map(function (item) {
return (
<ListItemComponent
key={index++}
item={item}
whenItemClicked={this.handleItemClick}
className={this.state.selectedItems.indexOf(item) != -1 ? "active" : ""}
/>);
}.bind(this));
} else {
list = this.props.listItems.map(function (item) {
return (
<ListItemComponent
key={index++}
item={item}
whenItemClicked={this.handleItemClick}
className={this.state.selectedItems.indexOf(item) != -1 ? "active" : ""}
/>);
}.bind(this));
}
return <div className="btn-group bootstrap-select form-control">
<DropDownButtonComponent
whenClicked={this.handleClick}
title={this.state.title}
/>
<ul className={"dropdown-menu inner dropdown-menu " + (this.state.open ? "show" : "") }>
{this.props.search? <li><input type="text" style={{margin:"auto", maxWidth:"96%"}} onChange={this.handleTextChange} placeholder="Search"/></li> :""}
<li className="disabled"><a>Select from below list {this.props.multiple ? <i title="clear all" style={{fontSize:"15px"}} onClick={this.clearSelected} className="text-danger fa fa-ban pull-right"></i>: ""}</a></li>
{list}
</ul>
</div>
}
});
ListItem.jsx
import React from 'react';
module.exports = React.createClass({
handleClick: function() {
this.props.whenItemClicked(this.props.item);
},
render: function() {
return <li onClick={this.handleClick} className={this.props.className}>
<a>{this.props.item}</a>
</li>
}
});
DropDownButton.jsx
import React from 'react';
module.exports = React.createClass({
render: function() {
return <button onClick={this.props.whenClicked} className="btn dropdown-toggle btn-default" type="button">
<span className="filter-option pull-left">{this.props.title}</span>
<span className="bs-caret"><i className="fa fa-chevron-down"></i></span>
</button>
}
});
Advance thanks to the one who helps me. Thank you.
I think you have a prop/state design issue. Your this.props.ifListChanged(this) inside your render function is very suspicious. Your render function should NOT need to signal anything to parent. Parent already knows all the props it sent down, and if parent needs to know about the state, then it most likely should not be state in the first place.
From what I can gather from your code,
your List component receives an unfiltered list of items as props
and it has a state that keeps track of filtereditems and of selecteditems.
This is a nice setup if the result of both need to be sent somewhere with another action inside the component itself (e.g. a process-selection button or something).
Then (and only then) would you send the state to parent or to somewhere else.
If the parent needs to know about both all the time (for instance when the process-button or process-action is somewhere else), then it is better to:
define some handleFilterUpdate and 'handleSelectionUpdate` methods inside the parent and pass these as props to the child.
also pass the filtered list and selection from the parent to the child.
call the this.props.handleFilterUpdate and 'this.props.handleSelectionUpdate` from the child whenever something happens with selection or filters.

React: Binding array to dynamically added fields

Lets say I have a class with a state level array
ElementsClass = React.createClass({
getInitialState: function() {
return {
elements: []
}
},
addElement: function() {
var element = {
name: ""
};
},
render() {
return (
{this.state.elements.map(function (element, i) {
return <input value={element.name} />
}
)}
)
}
The idea being that I can dynamically add to the elements array and have a new input field appearing.
How do I bind the data so that I am able to change the value in the input field and have that reflect automatically in the correct element in the elements array?
To dynamically sync your inputs with your state array you can use someting called linkState from the react-catalyst package. Once you've installed it with npm you can use it in the following way:
//need to import
import Catalyst from 'react-catalyst';
ElementsClass = React.createClass({
// mixin the linkedstate component
mixins : [Catalyst.LinkedStateMixin],
getInitialState: function() {
return {
elements: []
}
},
addElement: function() {
var element = {
name: ""
};
//add to elements array
this.state.elements.push(element);
//let react know to rerender necessary parts
this.setState({
elements : this.state.elements
});
},
render() {
return (
{this.state.elements.map(function (element, i) {
//use the linkState method
return <input valueLink={this.linkState('elements.'+i+'.name')} />
}
)}
)
}
The reason we need the react-catalyst package is that natively React's valueLink will only link top level state items, in your case elements. Obviously this isn't particularily useful but thankfully it's a fairly easy problem to solve.
Note: for iterated items like your element inputs, you need to provide a unique key. Something like the following (might need modifying to be more specific):
{this.state.elements.map(function (element, i) {
//use the linkState method
return <input valueLink={this.linkState('elements.'+i+'.name')} key={'elinput' + i} />
}
)}
This doesn't have any outward effect on your app, it's mostly to help react target the element internally.
If you want to do this with just ES5 and React, one solution would be this:
var ElementsClass = React.createClass({
getInitialState: function() {
return {
elements: []
}
},
createElement: function(){
var element = {
name: ''
};
this.setState({elements: this.state.elements.concat(element)});
},
updateElement: function(pos, event) {
var value = event.target.value;
var updatedElements = this.state.elements.map(function(element, i){
if (i === pos){
return {name: value};
}
return element;
});
this.setState({elements: updatedElements});
},
render: function() {
console.log(this.state.elements);
return (
<div>
{this.state.elements.map(function (element, i) {
var boundClick = this.updateElement.bind(this, i);
return <input key={i} onKeyUp={boundClick}/>
}.bind(this))}
<button onClick={this.createElement}>Add Element</button>
</div>
)
}
});
React.render(<ElementsClass />, document.getElementById('app'));
You want to treat component state as immutable, so you don't want to call a mutating method like push on elements.
These situations are handled easily with custom links packages.
State and Forms in React, Part 3: Handling the Complex State
import Link from 'valuelink';
// linked inputs will be deprecated, thus we need to use custom wrappers
import { Input } from 'valueLink/tags.jsx'
const ElementsClass = React.createClass({
getInitialState: function() {
return {
elements: []
}
},
render() {
// Take link to the element
const elementsLink = Link.state( this, 'elements' );
return (
<div>
{ elementsLink.map( ( elementLink, i ) => (
<Input key={ i } valueLink={ elementLink.at( 'name' ) } />
))}
<button onClick={ elementsLink.push({ name : '' })}>
Add Elements
</button>
</div>
);
}
});

React, Local cached variable and firing onclick on render

I am trying to make a local variable for each component instance, something like a little cache because it is storing information that toggles something and does not need to be on the state/store. So I have attempted it like so:
Setting a default prop to keep my info in :
getDefaultProps: function getDefaultProps() {
return {
showPreviewModal: { value: false,
writable: true
}
};
},
Setting a function to toggle this prop :
togglePreviewModal: function togglePreviewModal() {
this.props.showPreviewModal = !this.props.showPreviewModal;
},
And having that function fired by a click function
<button className="btn btn-default btn-blue previewAsset" onClick={() => this.togglePreviewModal() }>
I thought this would work in theory, but the onclick is firing immedietly on render. I googled this issue and it seems like the best result is to change the click function to :
{() => { this.props.togglePreviewModal() }}
However this does not work either, the click function is still firing immediately.
You can't directly mutate props like that - since props are explicitly passed down from a parent component, you'd need to change the prop at the source. Presumably, it originates from the state of a component somewhere up the hierarchy.
To do that, you'd pass down a function along with the prop that changes it at the source (using setState()). Here's an example:
var ParentComponent = React.createClass({
togglePreviewModal: function() {
this.setState({
showPreviewModal: !this.state.showPreviewModal
};
},
getInitialState: function() {
return {
// Unnecessary but providing for clarity
showPreviewModal: false
};
},
render: function() {
// This is for whatever values you were mapping over
var childComponents = ...map(function() {
return <ChildComponent togglePreviewModal={this.togglePreviewModal} />;
});
if (this.state.showPreviewModal) {
return (<div>
<Modal />
{childComponents}
</div>);
} else {
return (<div>
{childComponents}
</div>);
}
}
});
var ChildComponent = React.createClass({
render: function() {
return <button
className="btn btn-default btn-blue previewAsset"
onClick={this.props.togglePreviewModal} />;
}
});
Note that I'm not invoking the function in the onClick of the <button>, just passing in the prop (which is a function).

Call parent function passed as prop in React

I'm creating a survey-type app in React. The questions are arranged as items in a carousel.
When the user selects an answer - I AM able to change the state of the question (setting a button as active). However, I would also like to advance the carousel to the next item.
var Question = React.createClass({
getInitialState() {
return {
selectedIndex: -1
};
},
handleClick(index) {
this.setState({selectedIndex: index});
this.props.onQuestionAnswered();
},
render() {
var answerItems = answerChoices.map(function (answer) {
return (
<ReactBootstrap.ListGroupItem
key={answer.text}
text={answer.text}
active={answer.index == this.state.selectedIndex}
onClick={this.handleClick.bind(this, answer.index)}>
{answer.text}
</ReactBootstrap.ListGroupItem>
);
}.bind(this));
return (
<div>
<h3>{this.props.qText.text}</h3>
<ReactBootstrap.ListGroup>
{answerItems}
</ReactBootstrap.ListGroup>
</div>
);
}
});
var Carousel = React.createClass({
getInitialState() {
return {
index: 0,
};
},
handleSelect() {
this.setState({
index: 1
});
},
render() {
var questionItems = questionContent.map(function (question) {
return (
<ReactBootstrap.CarouselItem key={question.text}>
<Question qText={question}/>
</ReactBootstrap.CarouselItem>
);
});
return (
<ReactBootstrap.Carousel interval={false} activeIndex={this.state.index} onQuestionAnswered={this.handleSelect}>
{questionItems}
</ReactBootstrap.Carousel>
);
}
});
var App = React.createClass({
render() {
return (
<div>
<h4>Survey</h4>
<Carousel/>
</div>
);
}
});
React.render(<App/>, document.getElementById('content'));
I have a full JSFiddle available: http://jsfiddle.net/adamfinley/d3hmw2dn/
Console says the following when I try to call the function prop:
Uncaught TypeError: this.props.onQuestionAnswered is not a function
What do I have to do to call the parent function? Alternatively - is there a better pattern I should be using? (first time with React).
It looks like the error is coming from the Question component, which doesn't have the onQuestionAnswered prop. So you simply need to pass it in your questionItems map iteration.
var self = this;
var questionItems = questionContent.map(function (question) {
return (
<ReactBootstrap.CarouselItem key={question.text}>
<Question onQuestionAnswered={self.handleSelect} qText={question}/>
</ReactBootstrap.CarouselItem>
);
});

React: Getting an object on click

I have an array of objects which is passed as a property to a list that maps them to <li>.
I would like to be able, for any individual item, to click on an item from the list, and receive that object and then assign it to the root component's state - so I could then pass it on to another child comp.
var Menu = React.createClass({
render: function() {
return (<ul>
{
this.props.posts.map(function(post){
return <li><a onClick={function(e){console.log(e)}}>{post.title}</a></li>
})
}
</ul>)
}
})
https://jsfiddle.net/nbenita/yxw1z42q/
Thanks!
Pass a callback function into your Menu component as a prop and use Function.prototype.bind() to partially apply the relevant post object as an argument:
Updated fiddle: https://jsfiddle.net/yxw1z42q/2/
var Blog = React.createClass({
getInitialState: function() {
return {
selectedPost:this.props.posts[0]
};
},
onPostSelected: function(selectedPost) {
this.setState({
selectedPost: selectedPost
});
}
render: function() {
return (<div>
<Menu posts={this.props.posts} onClick={this.onPostSelected} />
<Post content={this.state.selectedPost} />
</div>)
}
})
var Menu = React.createClass({
render: function() {
return (<ul>
{
this.props.posts.map(function(post){
return <li><a onClick={this.props.onClick.bind(this, post)}>{post.title}</a></li>
}, this)
}
</ul>)
}
})
Further reading
React Docs - Communicate Between Components
react-training - Simple Component Communication

Categories

Resources