My html code contains a <ul id="users"></ul>, which is then populated dynamically with JS code
const li = document.createElement("li");
li.appendChild(document.createTextNode(`${nameInput.value} : ${emailInput.value}`)
I added a button in the html code to delete all users in that <ul>,as such: <button class="btn" id="del-user-list" onClick="deleteUserList()">Delete User List</button>.
My deleteUserList function in the JS code looks like this:
function deleteUserList() {
while (userList.firstChild != "") {
userList.firstChild.remove();
}
}
This works on the surface, however I realize that after the last child, my function will check once again for the value of a child that doesn't exist. I remember from studying C and playing with linked lists that you don't want to dereference a pointer that points to null.
Sure enough, when I look at the console I get
Uncaught TypeError: Cannot read properties of null (reading 'remove') at deleteUserList (main.js:31:25) at HTMLButtonElement.onclick ((index):29:77)
Is this a problem and what can I do about it? I just started playing with Javascript and don't have a good sense of how those things work right now.
Instead of comparing userList.firstChild to an empty string, you should compare it against null or omit the comparison operator entirely:
while (userList.firstChild != null)
// or
while (userList.firstChild)
The latter one works because converting null to a boolean value returns false
null != '' will always be true because userList.firstChild will never be an empty string anyway. It will either be a DOM node or null.
Related
I am fairly new to JavaScript and am going over some code. However there is one bit i am unsure about.
product = product !== null && product[0] !== null && product[0].id || "";
Where product is an array. Could someone please help me understand what this does. Any help would be much appreciated. Many thanks =)
One way to understand what this does it to run it and observe the result.
Here's a JSBin showing 3 examples - which produce a different outcome, depending on the initial value of product - https://jsbin.com/roruvecubi/edit?js,console
To further clarify this with an explanation...
It will attempt to evaluate that all the following premises are true at the same time and re-assign product to the value of id of the first object found (if these are found) or an empty string if otherwise.
product array is NOT null
AND
First element of product array is NOT null
AND
First element of product array is an object containing a truthy key-value pair with key id. I.e. First element could like this:
{
id: "someValue" // product[0].id would be a truthy value in this case
}
AND
If 3.0 is true - assign value of id. If 3.0 is NOT true (id: does not contain a truthy object, array, number, string, true - Google the precise definition of truthy), then just assign empty string "" and thus result will be product = "";
product !== null it checks if product is null if it is it will stop right here and not do the other calculations (this is practiced so you won't get undefined, in this case, hmm null)
product[0] !== null checks if null, so when .id you won't get an error can't find id of undefined / null
let usr = null
console.log(usr.id)
GIVES ERROR Uncaught TypeError: Cannot read property 'id' of null
enter code here
With a few words, these are some practices to check if the VARIABLE has unwanted values to stop the calculations right there and not get errors.
Some prefer to try catch v--
I am trying to get the length of an object but am getting this error message:
Uncaught TypeError: Cannot read property 'length' of undefined
I am trying to get the length and getting a popup when the length is zero.
I tried using this.props.storyboardTargets.length === 0
case 1: data not available so (!this.props.storyboardTargets)--> undefined
case 2: data was there and later deletd or cleared so need to check the length
Here is my code below:
handleShowPopupTarget = () => {
if (this.props.storyboardTargets && !this.props.storyboardTargets.length) {
console.log(this.props.storyboardTargets);
toastWarning(WARNING_MSG_NO_TARGET);
}
};
The way you have it written now does not handle the issue that this.props.storyboardTargets may be undefined. You need to update it like so:
handleShowPopupTarget = () => {
if (!this.props.storyboardTargets || !this.props.storyboardTargets.length) {
console.log(this.props.storyboardTargets);
toastWarning(WARNING_MSG_NO_TARGET);
}
};
This means if storyboardTargets is undefined, or its length is 0, toastWarning will fire.
As an alternative, you could define a default prop for your component of an empty array for storyboardTargets. That way it would never be undefined.
The error message means that storyboardTargets in undefined. Try seeing if storyboardTargets is being passed in to the component that contains your handleShowPopupTarget method.
use lodash's get, it simplifies a lot those kind of buggy checks:
_.get(this.props, 'storyboardTargets.length', 'default'); // you could use 0 instead of 'default' in your case
Your original code was
if (!this.props.storyboardTargets.length) {
And it fails because this.props.storyboardTargets is undefined and well you can not read properties of something that is undefined so it throws an error.
So after that you listened to advice and changed your code to be
if (this.props.storyboardTargets && !this.props.storyboardTargets.length)
So now this stops the undefined error from happening on this line because the truthy check on this.props.storyboardTargets prevents the evaluation of the second half of the code. So that means the code will not go into the if. but you WANT it to go into the if statement if it is not defined.
So what you need to do is change it to be an OR check so if it is NOT defined OR it does not have a length
if (!this.props.storyboardTargets || !this.props.storyboardTargets.length)
Now it goes into the if statement id it is undefined and will not throw the error.
The other solution is to see that it is undefined and set it to a default value
this.props.storyboardTargets = this.props.storyboardTargets || []
if (!this.props.storyboardTargets.length)
Now if the array is undefined, it sets it to an empty array and the if check will work correctly. Changing the data might not be the best solution if other things rely on undefined.
I have a projects object like so
projects: {
projectType: {id: 1, title:'something'},
budgetType: {id: 1, title:'something'},
projectStatus: {id: 1, title: 'something'}
}
and im rendering this in the render method.
<td>{this.props.projects.projectType.title}</td>
<td>{this.props.projects.budgetType.title}</td>
<td>{this.props.projects.projectStatus.title}</td>
This works fine, but sometimes the server sends in null when that object is not present as it is not a required field to be entered. So, this throws a "cannot read property of null error". I was using a ternary operator in each case to solve this error which doesnt look really nice. Is there any better way to solve this?
<td>{(this.props.projects.projectType.title)?this.props.projects.projectType.title: ''}</td>
EDIT:
I have a "ProjectList" component which lists all the project rows like so
//in render
<tbody>
{Object.keys(this.props.projects).map(this.renderProject)}
</tbody>
//renderProject Function
<Project key={key} project={this.props.projects[key]}/>
When accessing properties of null Javascript will throw this error. One usual pattern we use is like:
this.props.projects.projectType && this.props.projects.projectType.title
Here the second expression is evaluated only if first one is true. null and undefined are false so the second one won't be evaluated, an no error thrown.
This is because false && <whatever> === false
If projectType is not null, the value of the expression will be equal to the last item in the chain.
This can be chained in fancy ways like:
this && this.props && this.props.projects && this.props.project.projectType;
But it is always recommended to keep these checks inside the javascript file and use some derived attribute for the view.
I don't know if ampersand is a valid token in react expressions. Please refer to other answers on how such cases are handled in React way.
Why not create a simple helper method which accepts the property and returns either the value or an empty string if its null? You would still do the ternary operator but only in one place
I just observed the following weird behavior:
Deleting a variable that was not defined
> delete a
true
> delete a[0]
ReferenceError: a is not defined
> delete a.something
ReferenceError: a is not defined
> delete a.something[0]
ReferenceError: a is not defined
Deleting a subfield of a field that doesn't exist
> a = {}
{}
> delete a.foo
true
> delete a.bar.something
TypeError: Cannot convert null to object
> a.bar
undefined
I have two questions:
Why delete a works while a is not defined?
Why does deleting a.bar.something throw the error Cannot convert null to object instead of Cannot read property 'something' of undefined (because a.bar is undefined)?
According to documentation The delete operator removes a property from an object., so the answer for the first question would be that a is supposed to be a property of this object?
When using delete a; in c++ app, this error appears (and it's supposed to do) error: ‘a’ was not declared in this scope.
The answer is split into two. The first doesn't describe much but answer the question, while the latter goes into the nitty gritty details of the specification.
tl;dr
The first line works because in non-strict mode, trying to delete a variable just works.
The rest of the examples in the first section don't work because a isn't defined
delete a.foo works because there's no reason it shouldn't
delete a.bar.something throws because it's first trying to turn a.bar into an object before trying to access a.bar.something.
And now for something completely different
First, let's make clear that the two sections of code are conceptually different since the first talks about a variable which was not declared.
We'll be looking at how delete is specified quite a bit.
Let's start with the easier to understand parts:
> delete a[0]
ReferenceError: a is not defined
> delete a.something
ReferenceError: a is not defined
> delete a.something[0]
ReferenceError: a is not defined
All these lines are trying to do something with a, a variable which was not declared. Therefore, you get a ReferenceError. So far so good.
> delete a
true
This goes into the 3rd clause of the delete statement: a is an "unresolved reference", which is a fancy way of saying "it wasn't declared". Spec says to simply return true in that case.
> a = {}
{}
> delete a.foo
true
This one's intuitive as you expect (probably), but let's dive into it anyway. delete obj.property goes into the 4th clause:
Return the result of calling the [[Delete]] internal method on ToObject(GetBase(ref)) providing GetReferencedName(ref) and IsStrictReference(ref) as the arguments.
Welp that's a whole lot of uninteresting stuff. Let's just ignore everything after the [[Delete]] part, and just look at how [[Delete]] is specified. If I were to write it in js, it's be like this:
function Delete (obj, prop) {
var desc = Object.getOwnPropertyDescriptor(obj, prop);
if (!desc) {
return true;
}
if (desc.configurable) {
desc.magicallyRemove(prop);
return true;
}
throw new TypeError('trying to delete a non-configurable property, eh!?');
}
In the example, a does not have a property named foo, so nothing special happens.
Now it gets interesting:
> delete a.bar.something
TypeError: Cannot convert null to object
This happens because of some of the uninteresting things we ignored earlier:
Return the result of calling the [[Delete]] internal method on ToObject(GetBase(ref)) [...]
I highlighted the portion relevant to this specific snippet. Before we try to delete anything, spec tells us to call ToObject(GetBase(ref)), where ref = a.bar.something. Let's do that, then!
GetBase(a.bar.something) === a.bar
ToObject(a.bar) === ToObject(undefined) which throws a TypeError
Which explains the final behaviour.
Final note: The error message you've shown is misleading, since it says it tried to convert null into an object, which it didn't, as it tried to convert undefined into one. Latest chrome and firefox throw a more accurate one, but I think v8 only recently fixed the error message, so perhaps upgrading to node v11 will show a correct version.
I'm sharing experiments and readings, hope this will help!
1. Why delete a works while a is not defined?
If you try to read a property that isn’t there, JavaScript returns
“undefined”. This is convenient, but can mask errors if you’re not
careful, so watch out for typos!
Source: http://www.w3.org/wiki/Objects_in_JavaScript
"Delete" will remove both the value and the property, so as long as JavaScript returns a value for a, delete will work the same way that this example:
var a;
delete a;
Besides, as you said a is a property of this object. You can see this by testing this code:
> var a = {}
undefined
> a
{}
> this.a
{}
2. Why does deleting a.bar.something throw the error Cannot convert null to object instead of Cannot read property 'something' of undefined?
Please look at these examples:
NodeJS:
> var a = {}
undefined
> typeof a.b
'undefined'
> typeof a.b.c
TypeError: Cannot read property 'c' of undefined
at repl:1:12
at ......
> delete a.b.c
TypeError: Cannot convert null to object
at repl:1:10
at ......
Chrome console:
> var a ={}
undefined
> typeof a.b
"undefined"
> typeof a.b.c
Uncaught TypeError: Cannot read property 'c' of undefined VM335:2
> delete a.b.c
Uncaught TypeError: Cannot convert undefined or null to object
As you can see, both will manage a.b as an undefined value when testing typeof. But when deleting, chrome says that it may be undefined or null, while NodeJS consider this is a null value.
Would it be possible that NodeJS error is wrong formated?
I have this method
var link = this.find_first_link(selectedElem);
which should return an object. I'm not sure what should it return if no element is found - null, undefined or false? I've rejected 'false' option since I don't think it suits here so I'm choosing betwen null or undefined. I've read that 'undefined' should be used where some kind of exception or error occurs, so currently this method returns null. Is that OK?
Look at what is done in methods you have in your browser.
getElementById returns null when there is no element with the id provided.
That's what null has been designed for : to represent the absence of an object (typeof null is "object"). Use it when the expected returned type is "object" but you have no object to return. It's better than undefined here because you would have undefined before you even decided what to put in the variable or before you even call the function.
From the description of null in the MDN :
In APIs, null is often retrieved in place where an object can be
expected but no object is relevant.
Yes, use null.
Yes,
var link = this.find_first_link(selectedElem);
It will return NULL.