Error when updating ReactJS state with complex object - reactjs

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.

Related

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));
}

Use Ionic plugin in a Web Worker

How can I use an ionic plugin in a Web Worker? Specifically, org.apache.cordova.contacts if that helps.
Inspired by "Is it possible to run Angular in a web worker?" I'm trying to do:
self.window = self;
self.window.Event = function() {};
self.window.innerHeight = 1;
self.history = {};
self.document = {
readyState: 'complete',
addEventListener: function() {},
querySelector: function() {},
getElementsByTagName: function() {
return [];
},
createElement: function() {
return {
pathname: '',
setAttribute: function() {}
};
},
createEvent: function() {
return {
initEvent: function() {}
};
},
documentElement: {
style: {
transition: ''
}
},
head: {
children: [],
appendChild: function(child) {
importScripts('../../' + child.src);
child.onload();
}
},
body: {
classList: {
add: function() {},
}
},
};
importScripts('../../lib.js');
importScripts('../../cordova.js');
This gets it to load, but navigator.contacts.find is undefined after running that. :(
Is there an easier way? My end goal is to ingest the phone's contacts asynchronously.
(I need to do this because navigator.contacts.find() is a blocking call and to download the whole contact list is taking around 1 minute).

Refreshing map and markers with angular-google-maps

Hi I am using the angular-google-maps for my project. The html and js code is as follows
html:
<ui-gmap-markers models="apartments" coords="'loc'" icon="'assets/images/map_icon_normal.png'" idkey="'_id'"
fit="'false'" click="click">
<ui-gmap-windows show="'show'" >
<div ng-non-bindable>{{streetAddress}}</div>
</ui-gmap-windows>
</ui-gmap-markers>
</ui-gmap-google-map>
Script:
angular.module('myApp')
.controller('MapCtrl',
function ($scope, $routeParams, Map, uiGmapGoogleMapApi) {
$scope.apartments = [];
$scope.customIcon = "../../assets/images/map_icon_normal.png"
$scope.map = {
center: {
latitude: $routeParams.lat,
longitude: $routeParams.lon
},
zoom: 5,
bounds: {},
events: {
tilesloaded: function (map) {
//console.log('tiles loaded');
$scope.$apply(function () {
$scope.mapInstance = map;
//console.log(map.getBounds().getNorthEast());
$scope.searchbox.options.bounds = new google.maps.LatLngBounds($scope.mapInstance.getBounds().getNorthEast(),
$scope.mapInstance.getBounds().getSouthWest());
});
},
idle: function(map) {
$scope.map.refresh = false;
},
resize: function(map) {
console.log('resize');
},
dragend: function() {
}
},
markersEvents: {
click: function(marker, eventName, model, args) {
console.log('markerEvent click');
$scope.map.window.model = model;
$scope.map.window.show = true;
}
},
window : {
marker: {},
show: false,
closeClick: function() {
this.show = false;
},
options: {} // define when map is ready
},
control: {},
refresh: function () {
$scope.map.control.refresh();
}
}
uiGmapGoogleMapApi.then(function (map) {
$scope.getData(20, 0, map);
map.visualRefresh = true;
$scope.mapInstance = map;
})
$scope.getData = function(limit, offset, map) {
Map.getApartments($routeParams.lat, $routeParams.lon, limit, offset).success(function (data) {
///----- I get undefined error here---
$scope.map.control.refresh();
});
}})
}
I am not sure how to refresh the map with the new markers or even trigger an update to the map. I played around with 'control' and 'refresh' params of the ui-gmap-google-map but cannot get it to work.
You need to use uiGmapIsReady --- not uiGmapGoogleMapApi --- to wait for control objects to be augmented with their methods (such as refresh).
The uiGmapIsReady service provides a promise which resolves when all uiGmapGoogleMap directives have been fully initialized, while the uiGmapGoogleMapApi service resolves simply when the Google Maps JS API has loaded.
See this answer for an example of using uiGmapIsReady. And of course, don't forget the docs.
Regarding how to get updates to your models property on the scope to cause updates to your map, your code example needs to be fleshed out and cleaned up to help you there. (For example, what is the resolution of getApartments doing to update $scope.apartments? Or maybe asking that question is itself the answer?) Take a look at this fiddle, it might help you in the meantime.
Try dorebuildall attribute.
<ui-gmap-markers
models="apartments"
coords="'loc'"
icon="'assets/images/map_icon_normal.png'"
idkey="'_id'"
fit="'false'"
click="click"
dorebuildall="true">
<ui-gmap-windows></ui-gmap-windows>
</ui-gmap-markers>
More details at: http://angular-ui.github.io/angular-google-maps/

ReactJS can't access "this" methods

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`
});
}

Backbone.LayoutManager Delegated View Events

I've been working on a prototype Backbone application using Backbone.LayoutManager and I'm running into something I don't understand.
The scenario is that I have a form for adding "people" {firstname, lastname} to a list view, I save the model fine and the new item shows up in the list. I also have a remove function that works when after the page is refreshed, but if I try to delete the person I just created without a page refresh, the removeUser() function never gets called.
My code is below. Can someone help me out? I'm just trying to learn Backbone and if you have the answer to this question as well as any other criticisms, I'd be grateful. Thanks.
define([
// Global application context.
"app",
// Third-party libraries.
"backbone"
],
function (app, Backbone) {
var User = app.module();
User.Model = Backbone.Model.extend({
defaults : {
firstName: "",
lastName: ""
}
});
User.Collection = Backbone.Collection.extend({
model: User.Model,
cache: true,
url: "/rest/user"
});
User.Views.EmptyList = Backbone.View.extend({
template: "users/empty-list",
className: "table-data-no-content",
render: function (manage) {
return manage(this).render().then(function () {
this
.$el
.insertAfter(".table-data-header")
.hide()
.slideDown();
});
}
});
User.Views.Item = Backbone.View.extend({
template: "users/user",
tagName: "ul",
className: "table-data-row"
events: {
"click .remove": "removeUser"
},
removeUser: function () {
console.log(this.model);
this.model.destroy();
this.collection.remove(this.model);
this.$el.slideUp();
if (this.collection.length === 0) {
this.insertView(new User.Views.EmptyList).render();
}
}
});
User.Views.List = Backbone.View.extend({
initialize: function () {
this.collection.on("change", this.render, this);
},
render: function (manage) {
if (this.collection.length > 0) {
jQuery(".table-data-no-content").slideUp("fast", function() {
$(this).remove();
});
this.collection.each(function(model) {
this.insertView(new User.Views.Item({
model: model,
collection: this.collection,
serialize: model.toJSON()
}));
}, this);
} else {
this.insertView(new User.Views.EmptyList());
}
// You still must return this view to render, works identical to
// existing functionality.
return manage(this).render();
}
});
User.Views.AddUser = Backbone.View.extend({
template: "users/add-user",
events: {
"click input#saveUser": "saveUser"
},
render: function (manage) {
return manage(this).render().then(function () {
$("input[type='text']")
.clearField()
.eq(0)
.focus();
});
},
saveUser: function () {
var user = new User.Model({
firstName: $(".first-name").val(),
lastName: $(".last-name").val()
});
this.collection.create(user);
this
.$("input[type='text']")
.val("")
.clearField("refresh")
.removeAttr("style")
.eq(0)
.focus();
}
});
return User;
});
The problem turned out to be an incorrect response from the server. Once the server sent back the correct JSON object, everything worked correctly.

Resources