I'm accessing an API with ReactJS. What is the best way to stop React Component crashing when it's accessing a property in the object provided by the API that may be 'undefined'?
An example of an error is:
TypeError: Cannot read property 'items' of undefined
It looks like you're trying to access the property items of a variable x.
And if x is undefined, then calling x.items will give you the error you mentioned.
Doing a simple:
if (x) {
// CODE here
}
or
if (x && x.items) { // ensures both x and x.items are not undefined
// CODE here
}
EDIT:
You can now use Optional Chaining, which looks sweet:
if (x?.items)
In simple function you do it simply by if statement.
if(typeof x !=='undefined' && typeof x.item !=='undefined'){
}
in JSX you do it in this way.
render(){
return(
<div>
(typeof x !=='undefined' && typeof x.item !=='undefined')?
<div>success</div>:
<div>fail</div>
</div>
)
}
<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>
This post talks about a few error handling strategy in your react app.
But in your case, I think using try-catch clause would be the most convenient.
let results;
const resultsFallback = { items: [] };
try {
// assign results to res
// res would be an object that you get from API call
results = res.items;
// do stuff with items here
res.items.map(e => {
// do some stuff with elements in items property
})
} catch(e) {
// something wrong when getting results, set
// results to a fallback object.
results = resultsFallback;
}
I assume that you are using this only for one particular pesky react component. If you want to handle similar type of error, I suggest you use ReactTryCatchBatchingStrategy in the blog post above.
Best way to check for any such issue is to run your test code in google's console.
Like for a null check, one can simply check
if(!x)
or
if(x==undefined)
The optional chaining operator provides a way to simplify accessing values through connected objects when it's possible that a reference or function may be undefined or null.
let customer = {
name: "Carl",
details: {
age: 82,
location: "Paradise Falls" // detailed address is unknown
}
};
let customerCity = customer.details?.address?.city;
Simply you can use the condition
if (var){
// Statement
} else {
// Statement
}
Related
I'm trying to learn OOP through practice, but I'm pretty stuck at this point.
This is the code:
const itemEdit = () => {
let editIndex = buttonObj.editArr.indexOf(editID);
console.log(`the editIndex outside of the class is ${editIndex}`);
if (typeof editIndex != "undefined") {
editText = new htmlTextualizer(editIndex);
console.log(
"new class successfully created as variable is not 'undefined' type"
);
}
editText.printOut();
This is the class/constructor:
class htmlTextualizer {
constructor(curr) {
this.curr = curr;
}
printOut() {
console.log(this.curr);
}
}
The output is either 'undefined' or nothing at all. The logic generally works outside of the function, so I suspect it's something to do with the scope of initiation, but I simply fail to work my way around it. Assistance would be much appreciated. Thanks.
JavaScript's indexOf() returns -1 if no match is found. That check should look something like this:
if (editIndex > -1) {…}
I'm not sure if that will resolve your problem or not, but it's a problem in general.
Also, if that if statement is not true, and if editText is not defined somewhere outside what you've pasted here, there will be an error because editText is undefined (and doesn't have methods available).
There are several things that are unclear about your example, since you reference several undefined objects: buttonObj.editArr, editID, editText.
In general, I would approach testing for existence more carefully. You don't want to attempt to access the indexOf method on something undefined.
I'm not sure what your business logic is exactly, but here is how to do what I think it is: always create the new object, unless buttonObj.editArr contains editID.
Here is how to do that:
const itemEdit = () => {
if ( !buttonObj ||
!buttonObj.editArr ||
(typeof buttonObj.editArr !== "object") ||
!editID ||
(buttonObj.editArr.indexOf(editID) < 0) ) {
editText = new htmlTextualizer(buttonObj.editArr.indexOf(editID));
console.log("creating instance of class htmlTextualizer");
}
}
I am improving my React js code, using ESLint with eslint-config-airbnb, I am getting errors of type:
[eslint] Must use destructuring props assignment (react/destructuring-assignment)
I am able to overcome these errors by using JS Object destructuring and if necessary declaring additional variables.
In the following snippet, I use object destructuring to populate the cat variable. However, if I want to do an "if" statement, conditionally against the object destructuring output, I am unable to do that without doing a 2 step process where:
Declare the variable to use populating it via object destructuring
Use that variable in my "if" statement.
Is there some way to do this without having to declare this "temporary" variable, but still access the inner object property via object destructuring for use within for example an "if" statement.
I have made an attempt below, with the commented code, but it does not compile.
const animals = {
cat: "brown",
dog: "white"
};
let cat;
({
cat
} = animals);
console.log(cat);
if (cat === "brown") {
console.log("The cat is brown");
};
// Now, the same "if" statement, but this time I replace the variable called "cat" with lines 6 to 8 above
/*
if (({
cat
} = animals) === "brown")) {
console.log("The cat is brown");
};
*/
Here is the actual code which is having the error, I just constructed the example above to focus on the js syntax:
aTest() {
if (this.state.shouldToggle === true) {
this.setState({ activeTabKey: 'hello' })
} else {
clearInterval(this.state.timerId)
}
}
this.state.shouldToggle - is underlined red with the error "[eslint] Must use destructuring state assignment (react/destructuring-assignment)"
To me, it's very strange that ESLint complains about not using destructuring there. But apparently it does, which means your choices are:
Disable the rule if you don't like its requirements. (If it really requires use of destructuring in that code — and I have no reason to doubt your screenshot — the rule seems a bit silly to me, but that's neither here nor there.)
Since it's requiring you to use destructuring, in that example it's requiring you to use destructuring assignment (since you have no parameters to destructure), which means you have to have something to assign to, which means creating unnecessary variables/constants:
aTest() {
const {shouldToggle, timerId} = this.state;
if (shouldToggle === true) {
this.setState({ activeTabKey: 'hello' })
} else {
clearInterval(timerId)
}
}
That prevents repeating this.state, but makes you repeat shouldToggle and timerId instead, which doesn't seem like a useful trade-off (again, to me, but my opinion isn't what matters here, yours is).
As per your updated question, you should just be able to do:
aTest() {
const { shouldToggle, timerId } = this.state
if (shouldToggle) {
this.setState({ activeTabKey: 'hello' })
} else {
clearInterval(timerId)
}
}
Continuing with your previous try:
if (({ cat } = animals) === "brown")) {
console.log("The cat is brown");
};
Will never satisfy the condition.
When you assign a variable using destructuring syntax, it is comparing with object itself. Let me clarify you using simple tests:
if(({test} = {test:13})==13) {
console.log(test); // will not be logged
}
if(({test} = {test:13})==undefined) {
console.log(test); // will not be logged
}
if(({test} = {test:13})==true) {
console.log(test); // will not be logged
}
if(({test} = {test:13})==false) {
console.log(test);
}
if(JSON.stringify(({test} = {test:13})) == JSON.stringify({test:13}) ) {
console.log(test); // now, this will be logged
}
So, you're comparing brown == { cat: 'brown', dog: 'white' } which will never satisfy.
What you must do implement is to assign them in a variable using destructuring syntax as per ESLINT,
const { cat } = animals
if(cat === 'brown') { // Okay
If "a_b" does not exist then the following code throws back undefined - which is what I want:
var abc= json.reduce((a,c) => a.concat({xyz:c.a_b}), [])
However, if I do the following code and look for "media" within "a_b" that does not exist then I get a failure "Cannot read property 'media' of undefined".
var abc= json.reduce((a,c) => a.concat({xyz:c.a_b.media}), [])
Why is this the case?
In both cases "a_b" does not exist yet it is ok with the code if I just call that but not if I try and look for a property within it.
Is there a way to get around this?
For example, I am trying to use "|| null" but that doesn't seem to work within a concat, as below.
var abc= json.reduce((a,c) => a.concat({xyz:c.a_b.media || null}), [])
Don't use concat method it will create a new array instance, use push instead:
var abc = json.reduce((a, c) => {
a.push({ xyz: c.a_b && c.a_b.media });
return a;
}, []);
I'm new to using react.js, and am trying to write a re-usable component that has an optional property passed to it. In the component, that optional property pulls data from a db using meteor, then I want to check if a property exists on the returned object (parent_task exists on task), and if exists, adds a link. This seems fairly simple, but I keep getting errors. Does anyone have any suggestions on what I might be missing? Is there a jsx gotcha that I'm missing?
<Header task={params.task_id} /> // rendering component with property
// Task List Header
Header = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
var handle = Meteor.subscribe('tasks');
return {
taskLoading: ! handle.ready(),
task: Tasks.findOne({_id: this.props.task})
}
},
getParentTaskLink() {
if (!this.data.taskLoading) {
var current_task = this.data.task;
if (parent_task in current_task) { // or current_task.hasOwnProperty(parent_task)
console.log("parent_task exists!");
}
}
},
render() {
return (
<div className="bar bar-header bar-calm">
{this.getParentTaskLink()} // eventually return anchor element here
<h1 className="title">Hello World</h1>
</div>
)
}
});
what is the prop in question? how about
{this.props.propInQuestion ? link : null}
I figured this out. Apparently it was a syntax issue - you need to use a string when searching for properties in objects. The line below works:
if ('parent_task' in current_task)
For me works:
if ('myProperty' in this.props) {}
or
if (this.props.myProperty !== undefined) {}
or
if (this.props.hasOwnProperty('myProperty')) {}
Next condition will not work for number property, as 0 value will not work (such as for empty string):
if (this.props.MaxValue) {}
Check if a property exists using React.js
There are two options you can use. the && operator and If statement to check if the props exist.
Option 1 will check if the property exists then run the second part of the code. It works like an if without the if.
Option 1
this.props.property && this.props.property
Option 2
if(this.props.property){
this.props.property
}
This also works with function names.
You can use this also check to render components and tags.
This works for me
if(this.props.test === undefined){
console.log('props.test is not defined')
}
I suggest to try this elegant solution to check callback property on your component:
if(typeof this.props.onClickCallback === 'function') {
// Do stuff;
}
or applying destructuring:
const { onClickCallback } = this.props;
if(typeof onClickCallback === 'function') {
// Do stuff;
}
The most upvoted answer
props.propInQuestion ? 'a' : 'b'
Doesn't work if the prop is a boolean and you're trying to check for existence.
Based on How do I check if an object has a key in JavaScript? the fastest way is props.hasOwnProperty('propInQuestion'), with the caveat that this will not search the prototype chain.
In functional components, you can use like this.
if(props.myProperty){
//do something
}else{
//do something
}
if(props.hasOwnProperty('propertyName')){
//do something
} else {
//do something else
}
You need to return out of getParentTaskLink() with the link you need.
if (current_task.parent_task) {
return (link);
} else { return null; }
Is there something that I'm missing that would allow item to log as an object with a parameter, but when I try to access that parameter, it's undefined?
What I've tried so far:
console.log(item) => { title: "foo", content: "bar" } , that's fine
console.log(typeof item) => object
console.log(item.title) => "undefined"
I'll include some of the context just in case it's relevant to the problem.
var TextController = function(myCollection) {
this.myCollection = myCollection
}
TextController.prototype.list = function(req, res, next) {
this.myCollection.find({}).exec(function(err, doc) {
var set = new Set([])
doc.forEach(function(item) {
console.log(item) // Here item shows the parameter
console.log(item.title) // "undefined"
set.add(item.title)
})
res.json(set.get());
})
}
Based on suggestion I dropped debugger before this line to check what item actually is via the node repl debugger. This is what I found : http://hastebin.com/qatireweni.sm
From this I tried console.log(item._doc.title) and it works just fine.. So, this seems more like a mongoose question now than anything.
There are questions similar to this, but they seem to be related to 'this' accessing of objects or they're trying to get the object outside the scope of the function. In this case, I don't think I'm doing either of those, but inform me if I'm wrong. Thanks
Solution
You can call the toObject method in order to access the fields. For example:
var itemObject = item.toObject();
console.log(itemObject.title); // "foo"
Why
As you point out that the real fields are stored in the _doc field of the document.
But why console.log(item) => { title: "foo", content: "bar" }?
From the source code of mongoose(document.js), we can find that the toString method of Document call the toObject method. So console.log will show fields 'correctly'. The source code is shown below:
var inspect = require('util').inspect;
...
/**
* Helper for console.log
*
* #api public
*/
Document.prototype.inspect = function(options) {
var isPOJO = options &&
utils.getFunctionName(options.constructor) === 'Object';
var opts;
if (isPOJO) {
opts = options;
} else if (this.schema.options.toObject) {
opts = clone(this.schema.options.toObject);
} else {
opts = {};
}
opts.minimize = false;
opts.retainKeyOrder = true;
return this.toObject(opts);
};
/**
* Helper for console.log
*
* #api public
* #method toString
*/
Document.prototype.toString = function() {
return inspect(this.inspect());
};
Make sure that you have defined title in your schema:
var MyCollectionSchema = new mongoose.Schema({
_id: String,
title: String
});
Try performing a for in loop over item and see if you can access values.
for (var k in item) {
console.log(item[k]);
}
If it works, it would mean your keys have some non-printable characters or something like this.
From what you said in the comments, it looks like somehow item is an instance of a String primitive wrapper.
E.g.
var s = new String('test');
typeof s; //object
s instanceof String; //true
To verify this theory, try this:
eval('(' + item + ')').title;
It could also be that item is an object that has a toString method that displays what you see.
EDIT: To identify these issues quickly, you can use console.dir instead of console.log, since it display an interactive list of the object properties. You can also but a breakpoint and add a watch.
Use findOne() instead of find().
The find() method returns an array of values, even if you have only one possible result, you'll need to use item[0] to get it.
The findOne method returns one object or none, then you'll be able to access its properties with no issues.
Old question, but since I had a problem with this too, I'll answer it.
This probably happened because you're using find() instead of findOne(). So in the end, you're calling a method for an array of documents instead of a document, resulting in finding an array and not a single document. Using findOne() will let you get access the object normally.
A better way to tackle an issue like this is using doc.toObject() like this
doc.toObject({ getters: true })
other options include:
getters: apply all getters (path and virtual getters)
virtuals: apply virtual getters (can override getters option)
minimize: remove empty objects (defaults to true)
transform: a transform function to apply to the resulting document before returning
depopulate: depopulate any populated paths, replacing them with their original refs (defaults to false)
versionKey: whether to include the version key (defaults to true)
so for example you can say
Model.findOne().exec((err, doc) => {
if (!err) {
doc.toObject({ getters: true })
console.log('doc _id:', doc._id) // or title
}
})
and now it will work
You don't have whitespace or funny characters in ' title', do you? They can be defined if you've quoted identifiers into the object/map definition. For example:
var problem = {
' title': 'Foo',
'content': 'Bar'
};
That might cause console.log(item) to display similar to what you're expecting, but cause your undefined problem when you access the title property without it's preceding space.
I think using 'find' method returns an array of Documents.I tried this and I was able to print the title
for (var i = 0; i < doc.length; i++) {
console.log("iteration " + i);
console.log('ID:' + docs[i]._id);
console.log(docs[i].title);
}
If you only want to get the info without all mongoose benefits, save i.e., you can use .lean() in your query. It will get your info quicker and you'll can use it as an object directly.
https://mongoosejs.com/docs/api.html#query_Query-lean
As says in docs, this is the best to read-only scenarios.
Are you initializing your object?
function MyObject()
{
this.Title = "";
this.Content = "";
}
var myo1 = new MyObject();
If you do not initialize or have not set a title. You will get undefined.
When you make tue query, use .lean() E.g
const order = await Order.findId("84578437").lean()
find returns an array of object , so to access element use indexing, like
doc[0].title