I would like to pass a callback to a doubly nested component, and while I am able to pass the properties effectively, I can't figure out how to bind the callback to the correct component so that it's triggered. My structure looks like this:
-OutermostComponent
-FirstNestedComponent
-SecondNestedComponent
-DynamicallyGeneratedListItems
The List Items when clicked should trigger a callback which is the OutermostComponents method "onUserInput", but instead I get "Uncaught Error: Undefined is not a function". I suspect the problem is in how I am rendering the SecondNestedComponent inside the first, and passing it the callback. The code looks something like this:
var OutermostComponent = React.createClass({
onUserInput: //my function,
render: function() {
return (
<div>
//other components
<FirstNestedComponent
onUserInput={this.onUserInput}
/>
</div>
);
}
});
var FirstNestedComponent = React.createClass({
render: function() {
return (
<div>
//other components
<SecondNestedComponent
onUserInput={this.onUserInput}
/>
</div>
);
}
});
var SecondNestedComponent = React.createClass({
render: function() {
var items = [];
this.props.someprop.forEach(function(myprop) {
items.push(<DynamicallyGeneratedListItems myprop={myprop} onUserInput={this.props.onUserInput}/>);}, this);
return (
<ul>
{items}
</ul>
);
}
});
How do I correctly bind callbacks to the appropriate nested components?
You are passing this.onUserInput as a property to FirstNestedComponent. Therefore, you should access it in FirstNestedComponent as this.props.onUserInput.
var FirstNestedComponent = React.createClass({
render: function() {
return (
<div>
<SecondNestedComponent
onUserInput={this.props.onUserInput}
/>
</div>
);
}
});
For your reference, please check the implementation I've created at jsfiddle.net/kb3gN/12007
function ListenersService(){
var listeners = {};
this.addListener = function(callback){
var id;
if(typeof callback === 'function'){
id = Math.random().toString(36).slice(2);
listeners[id] = callback;
}
return id;
}
this.removeListener = function( id){
if(listeners[id]){
delete listeners[id];
return true;
}
return false;
}
this.notifyListeners = function(data){
for (var id in listeners) {
if(listeners.hasOwnProperty(id)){
listeners[id](data);
}
}
}
}
function DataService(ListenersService){
var Data = { value: 1 };
var self = this;
var listenersService = new ListenersService();
this.addListener = listenersService.addListener;
this.removeListener = listenersService.removeListener;
this.getData = function(){
return Data;
}
setInterval(function(){
Data.value++;
listenersService.notifyListeners(Data);
}, 1000);
}
var dataSevice = new DataService(ListenersService);
var World = React.createClass({
render: function() {
return <strong>{this.props.data.value}</strong>;
}
});
var Hello = React.createClass({
getInitialState: function() {
return {
data: this.props.dataService.getData()
};
},
componentDidMount: function() {
this.props.dataService.addListener(this.updateHandler)
},
updateHandler: function(data) {
this.setState({
data: data
});
},
render: function() {
return (
<div>
Value: <World data={this.state.data} />
</div>
);
}
});
React.renderComponent(<Hello dataService={dataSevice} />, document.body);
Related
i have some problem with react, i want to edit contact from contactList but i all the time error Uncaught TypeError: this.props.handleContactEditSubmit is not a function...
var ContactBox = React.createClass({
getInitialState: function () {
return { data: [] };
},
loadContactsFromServer: function () {
var xhr = new XMLHttpRequest();
xhr.open('get', this.props.url, true);
console.log(this.props.url);
xhr.onload = function () {
var data = JSON.parse(xhr.responseText);
this.setState({ data: data });
}.bind(this);
xhr.send();
},
componentDidMount: function () {
this.loadContactsFromServer();
window.setInterval(this.loadContactsFromServer, this.props.pollInterval);
},
handleContactAddSubmit: function (contact) {
var data = new FormData();
data.append('name', contact.name);
data.append('phoneNumber', contact.phoneNumber);
data.append('adress', contact.adress);
for (var pair of data.entries()) {
console.log(pair[0] + ', ' + pair[1]);
}
console.log(this.props.submitUrl);
var xhr = new XMLHttpRequest();
xhr.open('Post', this.props.submitUrl, true);
xhr.onload = function () {
this.loadContactsFromServer();
}.bind(this);
xhr.send(data);
},
handleContactEditSubmit: function (contact) {
var data = new FormData();
data.append('id', contact.phoneNumber);
data.append('name', contact.name);
var xhr = new XMLHttpRequest();
xhr.open('Post', this.props.editUrl, true);
xhr.onload = function () {
this.props.loadContactsFromServer();
}.bind(this);
xhr.send(data);
},
render: function () {
return (
<div className="contactBox">
<h1>Contacts</h1>
<ContactList data={this.state.data} editUrl="/contact/edit" handleContactSubmit={this.handleContactEditSubmit} />
<ContactForm onContactSubmit={this.handleContactAddSubmit} />
</div>
);
}
});
I want to edit contact from this class:
var Contact = React.createClass({
getInitialState: function () {
return { name: '', id: this.props.id };
},
handleNameChange: function (e) {
this.setState({ name: e.target.value });
},
rawMarkup: function () {
var md = new Remarkable();
var rawMarkup = md.render(this.props.children.toString());
return { __html: rawMarkup };
},
handleSubmit: function (e) {
e.preventDefault();
var name = this.state.name.trim();
var id = this.state.id;
if (name == '') return;
this.props.onContactSubmit({ name: name, id: id });
this.setState({ name: '' });
},
render: function () {
var md = new Remarkable();
return (
<form className="contact" onSubmit={this.handleSubmit}>
<input type="text"
placeholder="Your name"
defaultValue={this.props.name}
onChange={this.handleNameChange} />
<span dangerouslySetInnerHTML={this.rawMarkup()} />
<input type="submit" value="Post" />
</form>
);
}
});
But i displaying this Contacts like a list of contacts and i think this is problem, i don't know how to inherit funcion: handleContactSubmit.
var ContactList = React.createClass({
render: function () {
var contactNodes = this.props.data.map(function (contact) {
return (
<Contact name={contact.name} id={contact.contactId} key={contact.contactid} onContactSubmit={this.handleContactSubmit}>
{contact.phoneNumber}
</Contact>
);
});
return (
<div className="contactList">
{contactNodes}
</div>
);
}
});
You need to bind your functions to your component first or the handleSubmit function would be bound to the form:
this.handleSubmit = this.handleSubmit.bind(this);
normally it is done in the constructor
You can also skip the binding if you call the function like that:
onSubmit={ function(event) { return this.handleSubmit(event); } }
PS: i checked the wrong part of your code, but the issue is still the same. You need to bind the function to your component first:
this.handleContactEditSubmit = this.handleContactEditSubmit.bind(this);
I call handle inside the Contact class and it's working now.
var ContactList = React.createClass({
render: function () {
var handle = this.props.handleContactSubmit;
var contactNodes = this.props.data.map(function (contact) {
return (
<Contact onContactSubmit={handle} name={contact.name} id={contact.contactId} >
{contact.phoneNumber}
</Contact>
);
});
return (
<div className="contactList" handleContactSubmit={this.handleContactEditSubmit}>
{contactNodes}
</div>
);
}
});
Please forgive if I am way off target, but I am trying to set a component's state to a json object, so that I can render it with the component.
Here is what a currently have inside my component:
render: function() {
this.serverRequest = $.get(this.props.source, function (data) {
this.state.content = $.parseJSON(data);
}.bind(this));
return (
<div>
{Object.keys(this.state.content).map(function (key) {
return <div>Key: {key}, Value: {this.state.content[key]}</div>;
})}
</div>
);
With this code I currently get:
Uncaught TypeError: Cannot read property 'state' of undefined
Anyone have any insight as to why this isn't working?
The problem is, the this inside the $.get() is not in the React's scope. And calling setState() inside render will throw an error. The following should help...
var App = React.createClass({
getInitialState: function() {
return {
content: {},
}
},
componentDidMount: function() {
this.serverRequest()
},
serverRequest: function() {
var _this = this
$.get(this.props.source, function(data) {
_this.state.content = $.parseJSON(data);
})
},
render: function() {
return ( < div >
{
Object.keys(this.state.content).map(function(key) {
return <div > Key: {
key
}, Value: {
this.state.content[key]
} < /div>;
})
}
< /div >
);
}
})
I'm trying to build a list of thumbnail image components which when clicked will update the state of the parent. Eventually, I do get the desired behavior, but it takes several clicks and the effect is usually one-click behind the input. I'm speculating that this has something to do with how the functions are being passed as props, but am completely at a loss for what's going on.
EDIT: I think the issue is that the page renders before the setState changes the image state, which is why each click executes with the image that was passed via the previous click. I either need to fin a way to wait until the state updates to render, or do something in ComponentDidUpdate to rerender the page (which seems hackish but still a possibility).
This is the code I have so far:
var ImageSelector = React.createClass({
getInitialState: function(){
return{
imgState: "<%= image_path('image1.jpg') %>"
}
},
_changePicState: function(thumbnail){
var newImage = thumbnail.props.imageLink
this.setState({imgState: newImage})
},
_getThumbnails: function(){
console.log('_getThumbnails')
const thumbnailList = [
{id: 1, imageLink: "<%= image_path('image1.jpg') %>"},
{id: 2, imageLink: "<%= image_path('image3.jpg') %>"},
{id: 3, imageLink: "<%= image_path('image7.jpg') %>"},
]
return thumbnailList.map((e) => {
return (
<ImageThumbnail key={e.id} imageLink={e.imageLink} propFunc={this._changePicState}/>
)
});
},
render: function() {
const thumbnails = this._getThumbnails()
return (
<div>
{thumbnails}
<MyCanvasComponent ref="canvasComp" imageLink={this.state.imgState}/>
</div>
)
}
});
var ImageThumbnail = React.createClass({
_runPropFunc: function(){
this.props.propFunc(this)
},
render: function(){
return (
<img key={this.props.id} src={this.props.imageLink} className="thumbnail" onClick={this._runPropFunc} />
)
}
})
EDIT: Including the myCanvasComponent code below.
var MyCanvasComponent = React.createClass({
getInitialState: function(){
return {
currentImage: this.props.imageLink
}
},
componentDidUpdate: function(){
this._draw()
},
_draw: function(){
var draw = function(){
ctx.drawImage(objectImg, 100, 100);
}
var can = this.refs.canvas;
var ctx = can.getContext('2d');
var objectImg = new Image();
var imgPath = this.state.currentImage;
objectImg.src = imgPath
console.log('drawing ' + imgPath)
objectImg.onload = function(){
draw();
}
},
componentWillReceiveProps: function(){
this.setState({currentImage: this.props.imageLink});
},
componentDidMount: function(){
console.log('canvas rendered')
this._draw()
},
render: function() {
return (
<div>
<canvas ref='canvas' width={867} height={600}/>
</div>
);
}
})
The problem lies in your MyCanvasComponent. You're setting the new state in the componentWillReceiveProps lifecycle method by using this.props but this.props references the old props, the new props are passed as a parameter to the componentWillReceiveProps function
By the way, you don't need to hold the current image in the MyCanvasComponent since this state is already managed by the ImageSelector component, passing down the current image from ImageSelector to MyCanvasComponent is sufficient in this case :
var ImageSelector = React.createClass({
getInitialState: function(){
return{
imgState: "https://rawgit.com/gorangajic/react-icons/master/react-icons.svg"
}
},
_changePicState: function(imageLink){
this.setState({imgState: imageLink})
},
_getThumbnails: function(){
const thumbnailList = [
{id: 1, imageLink: "https://rawgit.com/gorangajic/react-icons/master/react-icons.svg"},
{id: 2, imageLink: "https://s3.amazonaws.com/media-p.slid.es/uploads/jhabdas/images/969312/react-logo-1000-transparent.png"},
{id: 3, imageLink: "http://felknar.com/images/icon-react-7b609cd3.svg"},
]
return thumbnailList.map((e) => {
return (
<ImageThumbnail key={e.id} imageLink={e.imageLink} onClick={this._changePicState}/>
)
});
},
render: function() {
const thumbnails = this._getThumbnails()
return (
<div>
{thumbnails}
<MyCanvasComponent imageLink={this.state.imgState}/>
</div>
)
}
});
var ImageThumbnail = React.createClass({
_runPropFunc: function(){
this.props.onClick(this.props.imageLink)
},
render: function(){
return (
<img key={this.props.id} width={50} height={50} src={this.props.imageLink} className="thumbnail" onClick={this._runPropFunc} />
)
}
})
var MyCanvasComponent = React.createClass({
_draw: function(){
var draw = function(){
ctx.drawImage(objectImg, 100, 100);
}
var can = this.refs.canvas;
var ctx = can.getContext('2d');
var objectImg = new Image();
var imgPath = this.props.imageLink;
objectImg.src = imgPath
objectImg.onload = function(){
draw();
}
},
componentDidUpdate: function(){
this._draw()
},
componentDidMount: function(){
this._draw()
},
render: function() {
return (
<canvas ref='canvas' width={867} height={600}/>
)
}
})
ReactDOM.render(<ImageSelector/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
First off, I would drop the habit of prefixing your function names with underscores. It serves no purpose: it doesn't actually make them private functions. It's code mess, basically. That's probably a bit subjective, but I thought I'd offer that up anyway. :)
Secondly, in ImageThumbnail, I wouldn't pass this back to the parent, when all you really need is the imageLink. So just pass that.
Try the code below. I also renamed the prop to onClick so it's clearer what's going on. When making your own components that have event handlers, try to stick to conventional names (onClick, onChange, onDeleteThumbnail). It'll make your life easier!
Side note: If you have time, try to get on board with the ES2015 class way of doing things.
var ImageSelector = React.createClass({
getInitialState: function () {
return {
imgState: "<%= image_path('image1.jpg') %>"
}
},
changePicState: function (imageLink) {
this.setState({ imgState: imageLink });
},
getThumbnails: function () {
console.log('getThumbnails');
const thumbnailList = [
{ id: 1, imageLink: "<%= image_path('image1.jpg') %>" },
{ id: 2, imageLink: "<%= image_path('image3.jpg') %>" },
{ id: 3, imageLink: "<%= image_path('image7.jpg') %>" },
];
return thumbnailList.map((e) => {
return (
<ImageThumbnail key={e.id} imageLink={e.imageLink}
onClick={this.changePicState} />
)
});
},
render: function () {
return (
<div>
<div>{this.getThumbnails()}</div>
<div>{this.state.imgState}</div>
</div>
)
}
});
var ImageThumbnail = React.createClass({
runPropFunc: function () {
this.props.onClick(this.props.imageLink);
},
render: function () {
return (
<img key={this.props.id} src={this.props.imageLink} className="thumbnail"
onClick={this.runPropFunc} />
)
}
});
I still didn't get rid of the underscores, but it's on the to-do list. I feel like this component might be getting a bit bloated, but it ensures that the new canvas element gets drawn after the state change. Previously, the state change would be queued and the new component would be rendered (along with the draw function which used to be in the MyCanvasComponent) and the state would change after rendering, so everything lagged behind by one click.
Thank you once again for your help!
var ImageSelector = React.createClass({
getInitialState: function(){
return{
imgState: "<%= image_path('image1.jpg') %>"
}
},
_draw: function(){
var draw = function(){
ctx.drawImage(objectImg, 100, 100);
}
var can = this.refs.canvas;
var ctx = can.getContext('2d');
var objectImg = new Image();
var imgPath = this.state.imgState;
objectImg.src = imgPath
objectImg.onload = function(){
draw();
}
},
componentDidUpdate: function(){
this._draw()
},
componentDidMount: function(){
this._draw()
},
_changePicState: function(imageLink){
this.setState({imgState: imageLink})
},
_getThumbnails: function(){
const thumbnailList = [
{id: 1, imageLink: "<%= image_path('image1.jpg') %>"},
{id: 2, imageLink: "<%= image_path('image3.jpg') %>"},
{id: 3, imageLink: "<%= image_path('image7.jpg') %>"},
]
return thumbnailList.map((e) => {
return (
<ImageThumbnail key={e.id} imageLink={e.imageLink} onClick={this._changePicState}/>
)
});
},
render: function() {
const thumbnails = this._getThumbnails()
return (
<div>
{thumbnails}
<canvas ref='canvas' width={867} height={600}/>
</div>
)
}
});
var ImageThumbnail = React.createClass({
_runPropFunc: function(){
this.props.onClick(this.props.imageLink)
},
render: function(){
return (
<img key={this.props.id} src={this.props.imageLink} className="thumbnail" onClick={this._runPropFunc} />
)
}
})
I have this following code
var SelectOption = React.createClass({
getInitialState: function() {
return {
data: []
};
},
handleemployeeChange: function() {
alert('sssss');
},
loadOptionfromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({
data: data
});
console.log(data);
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
componentDidMount: function() {
alert('sssss');
this.loadOptionfromServer();
//setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return ( < SelectOptionList data = {
this.state.data
}
/>
);
}
});
var SelectOptionList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(function(list) {
return ( < Addcontenttoselect id = {
list.emp_ide_id
}
option = {
list.emp_name
} >
< /Addcontenttoselect>
);
});
return ( < select id = "select1"
className = "form-control"
data - placeholder = "Basic Select2 Box"
onChange = {
this.handleemployeeChange
} > {
commentNodes
} < /select>
);
}
});
var Addcontenttoselect = React.createClass({
render: function() {
return ( < option value = "{this.props.id}" > {
this.props.option
} < /option>);
}
});
ReactDOM.render( < div className = "col-md-3" > < h3 > Select Employee to Review < /h3><SelectOption url="/appraisal / employeelist " pollInterval={70000} /></div>, document.getElementById('select-box'));
So this component creates a Select Tag in the browser , I want to take the Value of the selected option and Call another component which will create a Table from a data got from API
Any leads please let me know
Thanks
With react you have multiple ways to pass around data to your components, it depends heavily on the use and the complexity of your application.
If you have a lot of components which need to know about the state/data of another component you should look at application architectures like flux or redux. Facebooks Flux
For some applications a full data flow architecture can be overkill so it depends on how you design your components. A common pattern is to have one component who handles the state/interactivity of your application.
Your main component will hold all the business logic of your app and pass down functions to its child to e.g. change state.
You can read more about this here Facebook thinking react
I did a little fiddle which adresses your challenge:
Fiddle
var Select = React.createClass({
render: function() {
var selectOptions = this.props.options.map(function(optionData) {
return (
<option key={optionData.id} value={optionData.id}>
{optionData.name}
</option>
);
});
return (
<select
id="select1"
className="form-control"
placeholder="Basic Select2 Box"
onChange={this.props.onChange}
>
{ selectOptions }
</select>
);
}
});
var SelectApp = React.createClass({
// The main component holds the data
getInitialState: function() {
return {
data: [],
currentData: null
}
},
componentDidMount: function () {
this.loadOptions();
},
loadOptions: function () {
var _this = this;
return setTimeout(function() {
_this.setState({data: [
{
id: 1,
name: 'Foo Bar'
},
{
id: 2,
name: 'Bar Foo'
}
]});
}, 2000);
},
onChange: function (e) {
var employeeId = e.target.value,
_this = this,
mockedData = [
{
id: 1,
data: 'Good employee'
},
{
id: 2,
data: 'Not so good employee'
}
];
// Mocking an additional data fetch
setTimeout(function () {
var result = mockedData.find(function (employeeData) {
return (employeeData.id == employeeId);
});
_this.setState({
currentData: result
});
}, 2000);
},
renderResult: function () {
if (this.state.currentData) {
return (
<div>
<h4>Employee:</h4>
<p>{this.state.currentData.data}</p>
</div>
);
}
return;
},
render: function() {
return (
<div>
<div>
<h3> Select Employee to Review </h3>
<Select url={this.props.url} options={this.state.data} onChange={this.onChange}/>
</div>
{this.renderResult()}
</div>
);
}
});
ReactDOM.render(<SelectApp url="/appraisal / employeelist " pollInterval={70000} />, document.getElementById('container'));
Edit:
renderResult: function () {
if (this.state.currentData) {
return (
<loadUserAppraisal url="something" empid={this.state.currentData.id} />
);
}
my backbone collection collection doesn't populate when i just pass it in as props to a react component. I have tried first fetching the collection using componentDidmount and componentWillMount, but that still didn't populate the collection. If I test the code by setting a window variable pointing to DecksIndex and in the console tools call getInstance() and then fetch
,the data loads fine. my code is as follows:
//router.js
var DeckComponent = require("./views/deck.jsx")
var DecksIndex = React.createFactory(require("./views/decks.jsx"))
var decksCollection = require("./component/collections/decks.js");
module.exports = Backbone.Router.extend({
initialize: function(){
this.rootEl = document.getElementById('container');
},
routes: {
"":"index",
"decks/:id":"deckShow"
},
index: function(){
var decks = new DecksIndex({decks: decksCollection.getInstance()});
this._swapView(decks)
console.log("hooray!")
},
deckShow: function(id){
//var deck = Flashcards.Collections.decks.getOrFetch(id);
var showDeck = new DeckComponent();
this._swapView(showDeck);
},
_swapView: function(view){
if (this.currentView) {
React.unmountComponentAtNode(this.rootEl);
}
this.currentView = view
React.render(view, document.getElementById('container'));
}
});
//decks.js
var deck = require('../models/deck.js')
var decks = Backbone.Collection.extend({
url: "/api/decks",
model: deck,
getOrFetch: function(id){
var model = this.get(id);
var that = this;
if (model) {
model.fetch();
}else{
model = new deck({id: id})
model.fetch({
success: function(){
that.add(model)
}
})
}
return model;
},
parse: function (data) {
debugger;
return data.objects
},
});
decks.getInstance = _.memoize(function () {
return new decks();
});
module.exports = decks;
//decks.jsx
var DecksList = React.createClass({
render: function() {
return (
<div className="deck-list">
{
this.props.decks.map(function (deck) {
var title = deck.name
debugger;
return (
<div key={deck.id} className="note-summary">
{title}
</div>
);
})
}
</div>
);
}
});
module.exports = DecksList;
this is an example of a situation where a container component that manages state makes sense. If DecksList had a container that retrieved the collection when it mounted and only rendered DecksList once the data was available it would probably solve the problem. Here's a good article on the pattern: https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0