React error message missing - reactjs

I have a very simple React case where I have an error in the render function and nothing gets logged when the state gets updated to contain a bad value.
The error is that after the state is updated to contain a 'stuff' value that is not an array, which means that calling join(", ") on the 'stuff' value should fail, but nothing happens. React only keeps the data previously set in the getInitialState-function.
Am I doing something wrong here to loose errors messages or is this just how React works?
var someComp = React.createClass({
getInitialState: function () {
return {persons: [
{name: "Jane", stuff: ["a", "b"]},
{name: "Jim", stuff: ["c", "d"]}
]}
},
componentDidMount: function () {
superagent.get('/api/tags')
.end(function (res) {
// The res.body contains a stuff-value that is not an array.
this.setState({persons: res.body});
}.bind(this));
},
render: function () {
return R.table({}, [
R.thead({},
R.tr({}, [
R.th({}, "Name"),
R.th({}, "List of stuff")
])),
R.tbody({},
this.state.persons.map(function (person) {
return R.tr({}, [
R.td({}, person.name),
// If person.stuff is not an array, then no error is logged..!
R.td({}, person.stuff.join(", "))]);
}))
])
}
});

In my experience with React, it logs much more useful error messages if you build things step-by-step. Do that R.td({}, person.stuff.join(", "))] before the return statement, then use that result later on. If person.stuff isn't an array you'll get a better error message then.
More to the point, why do you want React to throw an error? If it's for development purposes, there are easier ways to check than waiting for error messages from React. Otherwise, shouldn't you be including a check to see if person.stuff is an array before attempting to use it as one?

Related

Tried to get frame for out of range index NaN (realtime database)

I'm trying to render some rows from firebase database, I'm getting this error:
TaskQueue: Error with task : Invariant Violation: Tried to get frame
for out of range index NaN
const { currentUser } = firebase.auth();
var userfavorites = firebase.database().ref(`/users/${currentUser.uid}/favorites/`);
userfavorites.once('value').then(snapshot => {
this.setState({ userfav: snapshot.val() })
})
...
<FlatList
data={this.state.userfav}
renderItem={({ item }) => (
<Text>{item.favdata}</Text>
)}
/>
I came across this error, I had a PHP back-end and trying to get json_encoded data into a FlatList.
Problem: The REST endpoint was returning an object eg
{"Total_Excavator":2,"Active_Excavator":2,"Total_load":6804}
Solution: Fixed it to return an array rather eg
[{"Total_Excavator":2,"Active_Excavator":2,"Total_load":6804}]
Take note of the Square Brackets.
I used $data[] = json_encode($excavatorArray) instead of
$data = json_encode($excavatorArray)
. Hope it helps someone one day
I had the same issue, it seems this problem is the reason of the object names.
In the image below you can see that as soon as you fetch snapshot from Firebase endpoint it comes with the id which is not recognized by React Native. And react acts like it's empty obj.
All you have to do is map the items of the object after fetching it like example below,
const fbObject = snap.val();
const newArr = [];
Object.keys(fbObject).map( (key,index)=>{
console.log(key);
console.log("||");
console.log(index);
newArr.push(fbObject[key]);
});
Just a slight modification to the answer from #mert. JavaScript's map operator returns an array; so, there is no need to push elements onto newArr. Also, the new array's elements are going to be missing the unique Firebase id. So, it should be added into the array element.
const fbObject = snapshot.val();
const newArr = Object.keys(fbObject).map((key) => {
fbObject[key].id = key;
return fbObject[key];
});
You'll end up with a newArray like this:
[
{
"id": "12345",
"name": "Jane Doe",
"age": 18
},
{
"id": "23456",
"name": "John Smith",
"age": 27
}
]
I had a similar problem and saved the flatlist's data in the state. You have to make sure that the data in the state is a list instead of an object:
this.state = {
...,
data: [], // <-- wrap your flatlist's data in there
}
When making changes to your data/the state, always use the callback function to perform something afterwards to keep your UI in sync:
this.setState({..., data}, () => yourCallback())

Firebase $remove() object from a nested array?

Firebase $remove() object from a nested array?
I can't get the Firebase $remove() method to work with nested arrays of objects. I'm making a movies database with an array of movie objects. Removing a movie from the array of movies is working:
$scope.deleteMovie = function() {
$scope.movie.$remove().then(function() {
console.log("Movie deleted.");
}, function(error) {
console.log(error);
});
};
That uses the $firebaseObject $remove() method which just hooks onto an object and removes it. The movie was identified in the array by its key, passing movie.$id into the URL and then grabbing the key from the URL to identify the movie and putting it in the $scope:
$scope.movie = $firebaseObject(ref.child($routeParams.id));
Users can add comments to movies. Each comment is an object in an array of comments. The array of comments is a property of the movie object, so it's a second-level nested array of objects. It seems like a better idea to use the $firebaseArray method $remove(recordOrIndex). I'll pass the comment through from the view:
$scope.deleteComment = function(comment) {
console.log(comment);
};
It passes through the comment:
Object {commentAuthor: "Eva Braun", commentDate: 1462461704268, commentText: "What's not to like?"}
Now I'll use $remove(comment) to delete the comment from the array:
$scope.deleteComment = function(comment) {
$scope.movie.movieComments.$remove(comment).then(function() {
console.log("Comment deleted.");
}, function(error) {
console.log(error);
});
};
The error message is "TypeError: $scope.movie.movieComments.$remove is not a function".
So $scope.movie.$remove() is a function but $scope.movie.movieComments.$remove is not a function.
I checked if this is an asynchronous problem but that doesn't seem to be the issue.
Firebase doesn't like dot notation for nested arrays of objects. I'll use the child() notation:
$scope.deleteComment = function(comment) {
$firebaseArray(ref.child($routeParams.id).child('movieComments').remove(comment)).then(function() {
console.log("Comment deleted.");
}, function(error) {
console.log(error);
});
};
The error message is "Error: Firebase.remove failed: first argument must be a valid function." The documentation says that the first argument must be either a record or an index. The comment passes through without its key, maybe that's the problem?
Let's try using an index instead of a record:
$scope.deleteComment = function(comment) {
$firebaseArray(ref.child($routeParams.id).child('movieComments').remove(0)).then(function() {
console.log("Comment deleted.");
}, function(error) {
console.log(error);
});
};
Same error message: "Error: Firebase.remove failed: first argument must be a valid function."
Let's try removing the comment from the view:
<div ng-repeat="comment in movie.movieComments">
<form>
<button ng-click="movie.movieComments.$remove(comment)">x</button>
</form>
</div>
That does nothing.
Let's try the $firebaseObject version of $remove():
$scope.deleteComment = function() {
$firebaseObject(ref.child($routeParams.id).child('movieComments').remove()).then(function() {
console.log("Comment deleted.");
}, function(error) {
console.log(error);
});
};
That works great! Except that it removes the entire array of comments, when I only want to remove one comment from the array. Let's add another .child():
$scope.deleteComment = function(comment) {
$firebaseObject(ref.child($routeParams.id).child('movieComments').child(comment).remove()).then(function() {
console.log("Comment deleted.");
}, function(error) {
console.log(error);
});
};
The error message is "Firebase.child failed: First argument was an invalid path: "undefined". Paths must be non-empty strings and can't contain ".", "#", "$", "[", or "]"".
I'm not identifying the comment correctly. How about using the key:
$scope.deleteComment = function(comment) {
console.log(comment);
$firebaseObject(ref.child($routeParams.id).child('movieComments').child(comment.$id).remove()).then(function() {
console.log("Comment deleted.");
}, function(error) {
console.log("Error, movie not deleted.");
console.log(error);
});
};
Same error message. The key isn't in the comment so the key isn't being passed through when the comment is passed through. I'll pass through the movie object and console log the movieComments array. The array has one object:
Object {-KH0VeEfwIgT1UOUrFzo: Object}
-KH0VeEfwIgT1UOUrFzo: Object
commentAuthor: "Eva Braun"
commentDate: 1462461704268
commentText: "What's not to like?"
Now I'll use $keyAt(recordOrIndex) to get the key:
$scope.deleteComment = function(movie, comment) {
console.log(movie.movieComments.$keyAt(comment));
};
"TypeError: movie.movieComments.$keyAt is not a function". Let's try .child() notation:
$scope.deleteComment = function(movie, comment) {
console.log(movie.child('movieComments').$keyAt(comment));
};
"TypeError: movie.child is not a function". Let's try that with an index number:
$scope.deleteComment = function(movie, comment) {
console.log(movie.child('movieComments').$keyAt(0));
};
"TypeError: movie.child is not a function". OK, $keyAt(recordOrIndex) and $remove(recordOrIndex) are both not working. That suggests there's something wrong with my record or index.
Any suggestions how to remove any object from a nested array in Firebase?
I am not certain with out seeing all of your code, but I have a feeling you are passing in an object with the final .child(comment) when firebase will only accept a string. See this line:
$firebaseObject(ref.child($routeParams.id).child('movieComments').child(comment).remove()).then(function() {
If you found the id or key string representation of what I assume is a comment object you could pass it into the remove function. That is why you are getting the error.
Also you can chain your children like
.child(`${$routeParams.id}/movieComments/comment`)
to keep everything a little cleaner
I figured it out. Firebase doesn't do dot notation so this doesn't work:
ng-click="movie.movieComments.$remove(comment)"
I set up an array of comments in the controller:
$scope.comments = $firebaseArray(ref.child($routeParams.id).child('movieComments'));
Then in the view I set up:
<div ng-repeat="comment in comments">
<button ng-click="comments.$remove(comment)"></button>
</div>
Works perfectly!
There's a mistake in the Firebase documentation. In the "Synchronized Objects" tutorial it gives this example:
{
"profiles": {
"physicsmarie": {
name: "Marie Curie",
dob: "November 7, 1867"
}
}
}
That's a JavaScript object. A Firebase object looks like this:
{
$id: "-KGSpDSJEtvCHetr5679",
{
"profiles": {
$id: "-KGSpDSJ43er5t3udghoe",
{
"physicsmarie": {
$id: "-KGSpDSJEtvCHNeXWriS",
{
name: "Marie Curie",
dob: "November 7, 1867"
}
}
}
}
}
}
This is why you can't use dot notation with Firebase. Firebase objects are nested double objects, with the key as the first property of the outer object and the inner object as the second property. You have to use .child() notation.

React shouldComponentUpdate doesn't detect array length change?

When Changing the length of an array passed through props, the 'shouldComponentUpdate' function can't detect the array length change.
I know that 'shouldComponentUpdate' can't detect changes in nested objects properties, but this is a simple array length!! is this a bug in React??
https://jsfiddle.net/ashraffayad/cLz1q8sv/
var ArrTest = React.createClass({
render: function() {
return <div >{this.props.arr}< /div>;
},
shouldComponentUpdate: function(nextProps) {
console.log(this.props.arr.length, nextProps.arr.length); // same length !!!
return true;
}
});
// - - - - app component
var App = React.createClass({
getInitialState: function() {
return {
arr: [1, 2, 3, 4]
};
},
render: function() {
return <ArrTest arr={ this.state.arr } />;
},
componentDidMount: function() {
var self = this;
setTimeout(function() {
self.state.arr.push(7);
self.setState(self.state);
}, 2000);
}
});
ReactDOM.render( < App /> ,
document.getElementById('container')
);
It's not a bug in React, it's an issue with your code.
You should never modify this.state values directly.
Try this:
componentDidMount: function() {
var self = this;
setTimeout(function() {
self.setState({arr: self.state.arr.concat([7])});
}, 2000);
}
It works. Because React doesn't clone props as it passes them down, and so changes to an array get reflect on all of its references.
I suggest you read more about immutability in Javascript.
In short, never do this.state.[anything].push/pop/shift/unshift(), never.
Do something like this instead:
var arr = this.state.arr.slice(); // Create a copy of the array
arr.push(2); // do whatever you want to do
this.setState({ arr: arr }); // pass changes to React
Just because you have two references (this.props.arr, nextProps.arr) does not mean you have two instances.
When you mutate the array with push, you modify the instance. When shouldComponentUpdate runs it compares the references and because they point to the same instance, the array lengths are the same.
If you want to pass down a new array with different elements or properties, then you need to create a new array too.
It's quite easy to substitute push for concat.
setTimeout(function() {
self.setState({
arr: self.state.concat([7])
}, 2000);
You're referencing the same array in your if, ie., you are modifying the same array instead of creating a new one and you're working two references to the same array in shouldComponentUpdate.
You should always treat props and state as immutable and therefore creating a new array with .concat instead of pushing onto the array in state will fix your current issue.
setTimeout(function () {
this.setState({arr: this.state.concat([7])});
}.bind(this), 2000);
If you'd have done this.props.arr === nextProps.arr within shouldComponentUpdate you'd see that the arrays would be equal to each other.

Rest call at componentDidMount

I am call a REST service in my React component's componentDidMount:
loadData: function () {
return $.getJSON(this.props.server+"/xxxxx/"+this.props.params.promotionid);
},
componentDidMount: function () {
this.loadData().success(function (data) {
console.log("After success");
console.log(data);
if(this.isMounted())
{
this.setState({
prom: data
//release: data.Release
});
}
}.bind(this))
}
This works even without documentation suggestion to include an if(this.isMounted()), there. Plesee note the commented line. We will need to comment this out in order to work. The thing is that if the data I get from the server has a subobject in it, it does not work even with the isMounted(). eg:
{
PromotionId: 1,
Description: "Hello three",
Category: "serviceid",
SpecialID: 23666,
ProjectManager: "Tarkidi Touvouda",
Requestor: "George Klapas",
Readiness: false,
SignOff: false,
SourceDestination: "developement-uat",
Status: "pending",
ReleaseId: 2,
Release: {
ReleaseId: 2,
ReleaseName: "Fall Second",
ReleaseDate: "2015-10-05T00:00:00",
ReleaseDeadline: "2015-10-05T00:00:00",
ModifiedDate: "2015-10-21T12:48:45.753"
},
PromotionAssets: null,
ModifiedDate: "2015-10-21T12:48:45.753"
}
I get the data at render() as follows
var p=this.state.prom;
var rel=p.Release;
I get an error when I try to access rel.ReleaseName that cannot find Releasename of undefined. The only way to make it work is removing the comment you saw (that is to say, having a separate field in state that will hold Release object) and then :
var rel=this.state.release;
Subsequent calls to Release object are successful. Why should I do that? I mean have a separate state field for this?
$.getJSON is an async call, which means that the execution continues while waiting for the response. So your script calls componentDidMount and after that continues with render() without waiting for the results.
Because of this in the first round of render(), the variable this.state.prom is undefined. Due to this a call to this.state.prom.Release throws an error, because you cannot get a property of an undefined object.
A simple solution might be to wait until the AJAX call has been finished an the state has been updated:
render: function() {
if(!this.state.prom) {
return <div>loading ...</div>;
}
// your code goes here
}

I'm not calling $apply explicitly but still get Error: [$rootScope:inprog] $apply already in progress

In a angular factory I have a method to create a new item, which has a connection to a user and a price to add to that users "items" array (like a shopping cart). So I have to see if the user is present in my the local users array if not then on the server and if not then create the user.
Code looks like this:
var saveItem = function (item) {
var user = filterUserById(item.ownerId);
if (user) {
user.createItem(item);
} else {
repository.getUserById(item.ownerId).then(
function (serverUser) {
var userViewModel = repository.getUserViewModel(serverUser);
userViewModel.createItem(item);
users.push(userViewModel);
}
, function () {
user = {
id: item.ownerId,
items: [
createItemDto(item)
]
};
repository.createUser({ id: user.id }, user);
users.push(repository.getUserViewModel(user));
});
}
};
No matter which of the "cases" occurs (user was found localy, on the server or was created and added) I get an error:
Error: [$rootScope:inprog] $apply already in progress
http://errors.angularjs.org/1.3.0-beta.18/$rootScope/inprog?p0=%24apply
I recon this may have to do with the fact that I'm using resources in my repository, but I don't think resource should (since it's a part of angular..). Here's the user.createItem method, code:
user.createItem = function (item) {
var resource = userResource
, itemDto = createItemDto(item)
, command = [{
Type: 'add',
Name: 'items',
Value: itemDto
}];
resource.createItem({ id: item.ownerId }, command);
this.items.push(itemDto);
};
Y U NO WERK!? PLS HLP! :'(
P.S. I don't have any explicit calls to apply, compile or digest anywhere in my code.
Found the problem! I had put a small code line to set focus on the correct input after the item was added and form was emptied. This consisted of a
$('selector').focus();
This was colliding with digest cycle... Solution:
$timeout($('selector').focus());
Try wrapping your call to user.createItem(item) in a $timeout function:
$timeout(function() {
user.createItem(item);
}, 0);
It's possible you could be triggering some other call to $scope.$apply() some other way.
Alternatively, try using $scope.$evalAsync(function())
Here's some good info: inprog

Resources