ReactJS can't access "this" methods - reactjs

I am trying to pass a method to a child component to handle onclick events.
I saw a lot of examples online, but I can't get it working.
When I am inside the render function of the parent and trying to pass "this.handleClick" to the child, handleClick is undefined.
Have a look at render method of ThumbList:
var Thumb = React.createClass({
handleClick: function() {
console.log(this)
console.log('handleClick of Thumb')
this.props.onClick()
},
render: function() {
return(
<div className="thumb" key={this.props.thumb.url}>
<a href='#' onClick={this.handleClick}>
<img src={'/img/rings/thumbs/'+this.props.thumb.url+'_thumb.jpg'} alt="Image">
</img>
</a>
</div>
);
}
});
var ThumbList = React.createClass({
handleClick: function (id) {
console.log('click of ThumbList');
},
loadFromServer: function() {
$.ajax({
url: 'rings/imgs/5',
dataType: 'json',
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error('rings/imgs/5', status, err.toString());
}.bind(this)
});
},
getInitialState: function(){
return {data: [] };
},
componentDidMount: function(){
this.loadFromServer();
setInterval(this.loadFromServer, 2000);
},
render: function() {
var handlefunc=this.handleClick
var thumbsNodes = this.state.data.map(function(thumb) {
console.log(this.handleClick) // is Undefined!
console.log(handlefunc) // is working
return (
<Thumb thumb={thumb} key={thumb.url} onClick={handlefunc.bind(this,thumb.url)}/>
);
});
return(
<div className="col-md-1 col-md-offset-1" id='thumbs'>
{thumbsNodes}
</div>
);
}
});
Any idea what I might be missing?

If you're using a compiler like Babel as part of your development workflow, I'd suggest using arrow functions:
var thumbsNodes = this.state.data.map((thumb) => {
console.log(this.handleClick);
return <Thumb thumb={thumb} key={thumb.url}
onClick={this.handlefunc.bind(this,thumb.url)}/>;
});
As you can see, it's a nice compact syntax. The arrow function will preserve the this context for you. The Babel compiler produces JavaScript that uses a closure:
var thumbsNodes = this.state.data.map(function(thumb) {
var _this = this;
console.log(_this.handleClick);
return <Thumb thumb={thumb} key={thumb.url}
onClick={_this.handlefunc.bind(_this,thumb.url)}/>;
});

this is undefined because the map callback does not know what it is. The simplest way to solve this is to pass a second argument, and it will use that as this in the callback:
var thumbsNodes = this.state.data.map(function(thumb) {
console.log(this.handleClick)
return <Thumb thumb={thumb} key={thumb.url} onClick={handlefunc.bind(this,thumb.url)}/>
}, this)
More: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

You need to grab a reference to this so it is the correct context when you call it.
Try
render: function()
{
var handlefunc = this.handleClick; // i assume this is just for debugging the issue
var self = this;
var thumbsNodes = this.state.data.map(function(thumb)
{
console.log(self.handleClick) // note the use of `self`
});
}

Related

Using AngularJS component props

I'm new to angularJS, and now I'm trying to realize some parts.
The questions is: how do I get access to callback onFinish() which is passed to component "my-timer" and run it? this.onFinish() returns the error.
Here is my markup:
<div ng-app="app" ng-controller="MyCtrl as myCtrl">
<div>
Status: {{myCtrl.status ? myCtrl.status : 'Waiting...'}}
</div>
<div>
<button ng-click="myCtrl.addTimer(5)">Add timer</button>
</div>
<div ng-repeat="timer in myCtrl.timers">
<div>
<h3>Timer {{timer.id}}</h3>
<button ng-click="myCtrl.removeTimer($index)">X</button>
<my-timer id="{{timer.id}}" start-seconds="{{timer.seconds}}" on-finish="myCtrl.onFinish(endTime)"></my-timer>
</div>
</div>
</div>
And here is index.js
var app = angular.module('app', []);
app.controller('MyCtrl', class {
constructor($scope) {
this.status = null;
this.timerId = 0;
this.timers = [];
this.addTimer(10);
this.addTimer(3);
console.log($scope);
}
addTimer(seconds) {
this.timers.push({
id: this.timerId++,
seconds
});
}
removeTimer(index) {
this.timers.splice(index, 1);
}
onFinish(endTime){
this.status = `Timer finished at ${endTime}`;
console.log(endTime);
}
});
app.component('myTimer', {
bindings: {
id: '#',
startSeconds: '#',
onFinish: '&',
},
controller: function($interval, $scope) {
this.endTime = null;
this.$onInit = function() {
this.countDown();
};
this.countDown = function() {
$interval(() => {
this.startSeconds = ((this.startSeconds - 0.1) > 0) ? (this.startSeconds - 0.1).toFixed(2) : 0;
}, 100);
};
},
template: `<span>{{$ctrl.startSeconds}}</span>`,
});
And here is jsFiddle
this.$onInit = function() {
this.countDown();
};
this.onFinish('1');
The problem here is that you tried to execute this.onFinish right in controller's body. And that wont work that way. If you want this function to be called during initialization, move it to $onInit
this.$onInit = function() {
this.countDown();
this.onFinish('1');
};
Otherwise, call it from another component method. You can only declare variables and component methods in controller body, but not call functions.

Uncaught TypeError: this.state.stock.map is not a function in React

new to react, have seen some of the similar title as below but still could not clarify with the error getting on the console saying Uncaught TypeError: this.state.stock.map is not a function . When I use the similar code in the another api call then there is no such error coming. The code is as below for the same. If I am assigning data from the get method to stock the also its not working and giving the same issue. What is the problem ? a few links that I referred.
Link 1
Link 2
Image after discussion.
var StockData = React.createClass({
getInitialState: function() {
return {
stock: []
};
},
componentDidMount: function() {
this.serverRequest = $.get(this.props.source, function (data) {
var old = JSON.stringify(data).replace("//", ""); //convert to JSON string
var obj = JSON.parse(old); //convert back to JSON
console.log(obj);
this.setState({
stock: obj
});
}.bind(this));
},
componentWillUnmount: function() {
this.serverRequest.abort();
},
render: function() {
return (
React.createElement("div",null,
React.createElement("ul",null,
this.state.stock.map(function (listValue){
return React.createElement("li",null,listValue.t,"( ",listValue.e," )-"," Current Price :",listValue.l_cur," Date :",listValue.lt
);
})
)
)
);
}
});
ReactDOM.render(React.createElement(StockData, { source: "http://www.google.com/finance/info?q=NSE:ATULAUTO,WIPRO,INFY" }),document.getElementById('proper-list-render3'));
Replace render function with stored this as self
render: function() {
var self = this;
return (
React.createElement("div",null,
React.createElement("ul",null,
self.state.stock.map(function (listValue){
return React.createElement("li",null,listValue.t,"( ",listValue.e," )-"," Current Price :",listValue.l_cur," Date :",listValue.lt
);
})
)
)
);
}
I finally got the solution to the problem. The data received after calling the url is received in the string format and not JSON. The correct code is as below and now it is working fine. Thanks #Piyush.kapoor for the help.
componentDidMount: function() {
this.serverRequest = $.get(this.props.source, function (data) {
var old = data.replace("//", "");
this.setState({
stock: JSON.parse(old)
});
}.bind(this));
}

Error when updating ReactJS state with complex object

When I do something like:
getInitialState: function() {
return { previews: [], isLoading: true, error: "", nextCursor: "" };
},
componentDidMount: function(){
$.ajax("/my-url", {
method: "GET",
success: this.previewsReceived,
failure: this.previewsFailedToReceive
});
},
previewsReceived: function(previews){
var tmpState = { isLoading: false, previews: previews.data, nextCursor: previews.next_cursor, error: "" };
this.setState(tmpState);
},
previewsFailedToReceive: function(_){
this.setState(Object.assign({}, this.state, { error: "", isLoading: false, previews: [], nextCursor: "" }));
},
I get the following reactJS error:
Uncaught [object Object]
on line 1093 in the react.js library (in the method invariant).
If I am not passing any complex object (inside my previews data) to the state however, I do not get the error.
Any idea about what I am doing wrong?
Edit: Here is the whole component, addressing the first answer, I still get the same errors.
var Creatives = React.createClass({
getInitialState: function() {
return { previews: [], isLoading: true, error: "", nextCursor: "" };
},
componentDidMount: function(){
$.ajax("/my-url, {
method: "GET",
success: this.previewsReceived.bind(this),
failure: this.previewsFailedToReceive.bind(this)
});
},
previewsReceived: function(previews){
var tmpState = { isLoading: false, previews: previews.data, nextCursor: previews.next_cursor, error: "" };
this.setState(tmpState);
},
previewsFailedToReceive: function(_){
this.setState({ error: "", isLoading: false, previews: [], nextCursor: "" });
},
render: function() {
return <ul>
{this.state.previews.map(function(creative) {
return <li key={creative.tweet_id} style="width: 450px">
<input type="checkbox" style="float:left;margin-top: 10px" />
<CreativePreview creative={creative} /></li>;
})
}
</ul>;
}
});
I also get the following warning when I call bind:
Warning: bind(): You are binding a component method to the component.
React does this for you automatically in a high-performance way,
so you can safely remove this call. See Creatives
Edit2: I found out that removing most of the render method 'fixes' the error. So I am gonna post the definition of that component too:
var CreativePreview = React.createClass({
render: function() {
return <iframe
id={ 'iframe-tweet-id-'+ this.props.creative.tweet_id }
dangerouslySetInnerHTML={this.props.creative.preview}>
</iframe>;
}
});
I don't think dangerouslySetInnerHtml works like you want it to on <iframe> elements.
You can populate it directly by creating a ref to your <iframe> element:
var CreativePreview = React.createClass({
componentDidMount: function(){
var frame = React.findDOMNode(this.refs.myFrame);
frame.document.body.innerHTML = this.props.creative.preview;
},
render: function() {
return <iframe ref='myFrame' />;
}
});
The error is most likely in CreativePreview. Try to remove it to see if it fixes your problem.
Side note: Object.assign() calls are unnecessary with React's setState. The setState method will automatically merge with the current state.
EDIT:
It seems that React will now auto-bind this for component functions.
EDIT 2:
Now we can see CreativePreview.
There were several errors in my code:
Inline class and style in html tags were assuming actual html conventions and not jsx format.
The iframe was very hard to implement in reactJS. I found a workaround inspired from https://github.com/ryanseddon/react-frame-component but since it didn't work out of the box, I used vanilla Javascript in it:
--
var CreativeFrame = React.createClass({
render: function() {
var style = { width: '420px', border: 'none', height: '280px' };
return <iframe
style={style}
className="tweet-content"
id={ "iframe-tweet-id-" + this.props.creative.tweet_id }>
</iframe>;
},
componentDidMount: function() {
this.renderFrameContents();
},
renderFrameContents: function() {
// Proof that good old Javascript >> Any library
var iframe = document.getElementById("iframe-tweet-id-" + this.props.creative.tweet_id);
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
iframeDoc.body.innerHTML = this.props.creative.preview;
},
componentDidUpdate: function() {
this.renderFrameContents();
},
componentWillUnmount: function() {
var doc = ReactDOM.findDOMNode(this).contentDocument;
if (doc) {
ReactDOM.unmountComponentAtNode(doc.body);
}
}
});
If anybody knows about improvements on it, let me know.

How to tell different component when add to same listener in React

All:
I am pretty new to React and FLUX, when I tried to follow FLUX TodoMVC example and make a example, the problem is I need to know how to mkae the click on button group only update its color text, but not affect the other component's color text( right now, if I click either div1's or div2's, both colortexts will change, I just want to according text changes color.): I know the store design right now is not for two components, but let assume the store can maintain a color list for according components, the question is still "How can I know which component is clicked"
// app.js
var Dispatcher = new (require("./Dispatcher"));
var assign = require("object-assign");
var React = require("react");
var ReactDOM = require("react-dom");
var EventEmitter = require("events");
var TodoStore = assign({}, EventEmitter.prototype, {
color: "black",
dispatcherIndex: Dispatcher.register(function(payload){
var type = payload.type;
var data = payload.data;
switch(type){
case "Change_Color": {
TodoStore.color = data.color;
TodoStore.emitChange();
break;
}
}
}),
getColor: function(){
return this.color;
},
emitChange: function(){
this.emit("CHANGE");
},
addChangeListener: function(callback){
this.on("CHANGE", callback);
},
removeChangeListener: function(callback){
this.removeListener("CHANGE", callback)
}
});
var ColorButtonGroup = React.createClass({
setColor: function(color){
Dispatcher.dispatch({
type:"Change_Color",
data: {
"color": color
}
});
},
render: function(){
return (
<div>
<button style={{color:"red"}} onClick={this.setColor.bind(this,"red")}>RED</button>
<button style={{color:"green"}} onClick={this.setColor.bind(this,"green")}>GREEN</button>
<button style={{color:"blue"}} onClick={this.setColor.bind(this,"blue")}>BLUE</button>
</div>
);
}
});
var ColorText = React.createClass({
getInitialState: function(){
return {
style: {
color: "black"
}
}
},
render: function(){
return (
<div style={this.state.style}>Color is: {this.state.style.color}</div>
);
},
componentDidMount: function(){
TodoStore.addChangeListener(this._onChange.bind(this));
},
componentWillUnmount: function(){
TodoStore.removeChangeListener(this._onChange.bind(this));
},
getColor: function(){
return TodoStore.getColor();
},
_onChange: function(){
this.setState({
style: {
color:this.getColor()
}
});
}
});
ReactDOM.render((<div>
<ColorButtonGroup></ColorButtonGroup>
<ColorText></ColorText>
</div>),
document.getElementById("div1"));
ReactDOM.render((<div>
<ColorButtonGroup></ColorButtonGroup>
<ColorText></ColorText>
</div>),
document.getElementById("div2"));
And the page:
// bundle is transpiled+browserify app.js and dependecies
<html>
<head>
<title>LEARN FLUX</title>
</head>
<body>
<div id="div1"></div>
<div id="div2"></div>
</body>
<script src="bundle.js"></script>
</html>
Assuming you update your store to save multiple colors:
var TodoStore = assign({}, EventEmitter.prototype, {
colors: {},
dispatcherIndex: Dispatcher.register(function(payload){
var type = payload.type;
var data = payload.data;
switch(type){
case "Change_Color": {
TodoStore.colors[data.colorKey] = data.color;
TodoStore.emitChange();
break;
}
}
}),
getColor: function(key){
return this.colors[key];
},
// ...
});
You need some way to identify which piece of data to update in the store. You could, for example, pass a property:
ReactDOM.render((<div>
<ColorButtonGroup colorKey="1" />
<ColorText colorKey="1" />
</div>),
document.getElementById("div1"));
ReactDOM.render((<div>
<ColorButtonGroup colorKey="2" />
<ColorText colorKey="2" />
</div>),
document.getElementById("div2"));
Then, when you dispatch the action, you pass along the key so the store knows what data to update:
setColor: function(color){
Dispatcher.dispatch({
type:"Change_Color",
data: {
colorKey: this.props.colorKey,
"color": color
}
});
},
Similarly, you'd look up the data in the appropriate store in ColorText:
getColor: function(){
return TodoStore.getColor(this.props.colorKey);
},

Backbone.js Uncaught ReferenceError: x is not defined

I am getting Uncaught ReferenceError: _auditNumber is not defined error while trying to bind my model to the view using backbone.js and underscore.js
<script id="searchTemplate" type="text/template">
<div class="span4">
<p>"<%= _auditNumber %>"</p>
</div>
<div class="span4">
<p>"<%= _aic %>"</p>
</script>
Collection
//Collection
var AuditsCollection = Backbone.Collection.extend({
initialize: function() {
this.on('add', this.render);
},
render: function() {
_.each(this.models, function (item) {
var _auditView = new AuditView({
model: item
});
$("#audits").append(_auditView.render().el);
});
},
});
Model
var Audit = Backbone.Model.extend({
url: function () {
return myUrl;
},
defaults: {
_auditNumber: "",
_aic: "",
},
parse: function (data) {
data.forEach(function (auditItem) {
var auditsCollection = new AuditsCollection();
auditsCollection.add(JSON.stringify(auditItem));
});
}
});
// Sub View
var AuditView = Backbone.View.extend({
className: 'row-fluid',
template: $("#searchTemplate").html(),
render: function () {
var tmpl = _.template(this.template);
this.$el.html(tmpl(this.model.toJSON()));
return this;
}
});
I know I am missing something simple, any help is appreciated.
2 problems (at least - you're kind of off in the weeds given how many backbone tutorials there are).
Your model URL is returning a list of results. That's what collections are for. Your model should fetch a single record and the parse method has to return the model's attribute data. If you stick with the tutorials, you won't need a custom url function and you won't need a custom parse function at all.
var Audit = Backbone.Model.extend({
url: function () {
//This needs to be a url like /audits/42 for a single record
return myUrl;
},
defaults: {
_auditNumber: "",
_aic: "",
},
parse: function (data) {
//this needs to return an object
return data[0];
}
});
You aren't passing a valid data object to your template function.
// Sub View
var AuditView = Backbone.View.extend({
className: 'row-fluid',
//compile template string into function once
template: _.template($("#searchTemplate").html()),
render: function () {
//render template into unique HTML each time
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});

Resources