I am trying to deploy my first javascript application, which is a Chrome extension. This simply generates random passwords and stores it with the url of current active tab.
App runs fine on local but after deploying it to Chrome, I got this error:
Uncaught TypeError: Cannot read properties of null (reading 'length') index.js:65 (anonymous function)
I am a beginner, so any kind of criticism about my code is highly appreciated. Thank you so much.
function render() {
*line65* **if(passwords.length === 0)** {
document.getElementById("saved-passwords-container").style.display= "none";
} else {
document.getElementById("saved-passwords-container").style.display= "unset";
}
let list = ""
**for (let i = 0; i < passwords.length; i )** {
list = `<div ><span>${passwords[i]}</span></br></br><span ><a target='_blank'href='${links[i]}'>${links[i]}</a></span></div>`
}
document.getElementById("passwords-el").innerHTML = list
}
Here is the full index.js file:
var characters = [];
for (var i=32; i<127; i )
characters.push(String.fromCharCode(i));
for( var i = 0; i < characters.length; i ){
if ( characters[i] === '<') {
characters.splice(i, 1);
i--;
}
}
for( var i = 0; i < characters.length; i ){
if ( characters[i] === '>') {
characters.splice(i, 1);
i--;
}
}
let pw1El = document.getElementById("pw1-el")
let pw1 = ""
let passwords = []
passwords = JSON.parse(localStorage.getItem("savedPasswords"))
let links = []
links = JSON.parse(localStorage.getItem("savedLinks"))
render()
document.getElementById("char-count-el").value = 20
document.getElementById("gen-btn").addEventListener("click", function() {
var charCount = document.getElementById("char-count-el").value
pw1 = ""
for(let i = 0; i < charCount; i ) {
let randomIndex = Math.floor(Math.random() * characters.length)
pw1 = (characters[randomIndex])
}
pw1El.textContent = pw1
})
document.getElementById("save-btn").addEventListener("click", function() {
passwords.push(pw1El.innerText)
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
links.push(tabs[0].url)
})
localStorage.setItem("savedPasswords", JSON.stringify(passwords))
localStorage.setItem("savedLinks", JSON.stringify(links))
render()
})
function render() {
**if(passwords.length === 0)** {
document.getElementById("saved-passwords-container").style.display= "none";
} else {
document.getElementById("saved-passwords-container").style.display= "unset";
}
let list = ""
**for (let i = 0; i < passwords.length; i )** {
list = `<div ><span>${passwords[i]}</span></br></br><span ><a target='_blank'href='${links[i]}'>${links[i]}</a></span></div>`
}
document.getElementById("passwords-el").innerHTML = list
}
document.getElementById("clear-btn").addEventListener("click", function() {
passwords = []
links = []
localStorage.setItem("savedPasswords", JSON.stringify(passwords))
localStorage.setItem("savedLinks", JSON.stringify(links))
render()
})
document.getElementById("copy-btn").addEventListener("click", function() {
var input = document.getElementById("pw1-el").textContent;
navigator.clipboard.writeText(input);
alert("Copied Text: " input);
})
index.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div >
<h1>Generate a</br>random password</h1>
<p>Never use an unsecure password again.</p>
<hr>
<div>
<label for="char-count-el">Character Count:</label>
<input type="number" id="char-count-el">
<button id="gen-btn"><span>Generate password</span></button>
</div>
<div>
<label>Your Password:</label>
<div >
<span id="pw1-el">...</span>
<button id="save-btn">SAVE</button>
<button id="copy-btn">COPY</button>
</div>
</div>
<div id="saved-passwords-container">
<hr>
<label>Saved Passwords:</label>
<div >
<div id="passwords-el">...</div>
<button id="clear-btn">CLEAR</button>
</div>
</div>
</div>
<script src="index.js"></script>
</body>
</html>
index.css
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
background-color: #ffffff;
color: white;
display: flex;
justify-content: center;
align-items: center;
}
h1::first-line {
color: white;
}
h1 {
color: #00ffaa;
margin-bottom: 5px;
line-height: 1;
}
label {
font-size: 11px;
display: block;
color: #D5D4D8;
margin-top: 10px;
}
input {
height: 38px;
border-radius: 5px;
border: none;
width: 70px;
padding: 0px 10px;
text-align: center;
background-color: #D5D4D8;
margin-right: 20px;
font-size: 14px;
}
.container {
background: #1F2937;
margin: 0;
padding: 10px 30px 40px;
width: 100%;
min-width: 500px;
box-shadow: 0px 10px 30px 10px #2640644b;
display: flex;
flex-direction: column;
}
.pw-container {
display: flex;
border-radius: 5px;
background-color: #3e4f66;
padding: 10px;
margin-top: 10px;
}
.password-line {
color: #00ffaa;
font-size: 16px;
padding: 5px 10px;
margin-top: 0px;
flex-grow: 1;
flex: 1 1 1;
min-width: 0;
word-wrap: break-word;
white-space: pre-wrap;
word-break: break-word;
}
#passwords-el {
padding-right: 30px;
flex-grow: 1;
flex: 1 1 0;
min-width: 0;
word-wrap: break-word;
white-space: pre-wrap;
word-break: break-word;
}
.saved-password-line {
color: #D5D4D8;
font-size: 14px;
padding: 10px 15px;
border-bottom: solid 1px #d5d4d814;
border-radius: 5px;
margin-bottom: 10px;
line-height: 0.9;
}
a {
color: #d5d4d872;
text-decoration: underline;
}
.side-btn {
font-size: 12px;
width: 60px;
border: none;
background: none;
color: #D5D4D8;
padding: 5px 10px;
border-radius: 5px;
justify-self: flex-end;
}
.side-btn:hover {
background-color: #ffffff28 ;
}
#gen-btn {
color: #ffffff;
background: #0EBA80;
text-transform: capitalize;
text-align: center;
width: 200px;
height: 40px;
padding: 10px 10px;
border: none;
border-radius: 5px;
margin-bottom: 10px;
margin-top: 10px;
transition: all 0.5s;
box-shadow: 0px 0px 30px 5px #0eba8135
}
#gen-btn:hover {
box-shadow: 0px 0px 30px 10px #0eba8157
}
#gen-btn span {
cursor: pointer;
display: inline-block;
position: relative;
transition: 0.5s;
}
#gen-btn span:after {
content: '\279c';
position: absolute;
opacity: 0;
top: 0;
right: -20px;
transition: 0.5s;
}
#gen-btn:hover span {
padding-right: 25px;
}
#gen-btn:hover span:after {
opacity: 1;
right: 0;
}
p {
color: #D5D4D8;
margin-top: 0px;
}
hr {
border-width: 1px 0px 0px 0px;
border-color: #95959576;
margin: 15px 0;
}
manifest.json
{
"manifest_version": 3,
"version": "1.0",
"name": "Password Generator",
"action": {
"default_popup": "index.html",
"default_icon": "icon.png"
},
"permissions": [
"tabs"
]
}
CodePudding user response:
I solved it. I understand that (please correct me if I'm wrong) if the local storage is empty, it does not return an empty array when parsed.
Apparently, when I do:
passwords = JSON.parse(localStorage.getItem("savedPasswords"))
passwords is no longer an array. I instead use:
passwords.push(JSON.parse(localStorage.getItem("savedPasswords")))
But that just pushes a nested array inside passwords. So I added a for loop, and used an if statement to address the initial error:
let locSavedPasswords = localStorage.getItem("savedPasswords")
if(locSavedPasswords !== null) {
for( var i = 0; i < (JSON.parse(locSavedPasswords)).length; i ){
passwords.push(JSON.parse(locSavedPasswords)[i])
}}
CodePudding user response:
Initially, savedPasswords
won't exist in localStorage
, so localStorage.getItem('savedPasswords')
will return null
.
You then do JSON.parse(null)
, which doesn't immediately crash because null
is first coerced to a string and becomes 'null'
which is then JSON-parsed and turns back to null
since the string with contents null
is valid JSON.
But you then do .length
on it and crash.
The solution is to handle the case where the item is not yet set and handle it like it was a JSON-stringified empty array. You can do so for example using the nullish coalescing operator ??
:
let passwords = JSON.parse(localStorage.getItem("savedPasswords") ?? '[]')
Or, you can keep initializing it with []
as you did before but wrap the assignment with the actual value in a condition:
let passwords = []
const json = localStorage.getItem('savedPasswords')
if (json !== null) {
passwords = JSON.parse(json)
}
Personally, what I like to do for structured data in localStorage
is something like this, which also handles the case that other things like invalid JSON somehow got stored there (without bricking the application):
let passwords = []
try {
const data = JSON.parse(localStorage.getItem('savedPasswords'))
if (Array.isArray(data)) passwords = data
} catch {}