Chrome Extension content script unable to find element by ID or class name, and “DOMContentLoaded” event listener never triggers

I am aware that the main cause of this issue is when the script runs before the DOM is fully loaded. So I have tried several solutions which I’ve found suggested to previous similar problems on here and elsewhere, including interval before loading, interval polling, mutation observer, event listener for DOM load, and other. Nothing worked, which is why this post will be a little long (sorry).

I get the error Uncaught TypeError: Cannot read properties of undefined (reading 'appendChild') at contentScript.js:3:15 which comes from the line of code which attempts to use the element that was unable to be found. Interestingly, I also get “undefined” when I run the same document.getElementByClassName() function manually in the chrome console with the same argument. However, once I Ctrl+F to find the element manually and run the same commands again, it somehow does find the elements and executes the code as intended. This way I was able to check that my code does actually do exactly what I want it to do, as long as it is able to find the actual element.

Now, this is the first extension I’ve ever actually tried making myself so I apologize for being out of my depth. However, I have managed to achieve certain other functionalities on the same exact webpage, so I know for sure that I am doing at least some things right. Unlike the main problem, these working functionalities are also achievable through the chrome console.

The code which works is the following:

var notificationsNumber = document.getElementById("notificationsNumber");
if (notificationsNumber) {
    notificationsNumber.parentNode.removeChild(notificationsNumber);
}

Simple enough, all it does is get rid of the annoying red number indicating the unread notifications.

So onto what I’m trying to do. Basically there is a chess website which offers chess opening courses and this page which lets you explore recommended moves from the courses on that website (requires an account, or can log in with google acc if you’d like to explore). All I want is to make a simple extension to add functionality to this webpage mostly for personal use. My very first step was to add a button to an existing array of buttons with the following code:

var buttondiv = document.getElementsByClassName("board-btns")[0];
buttondiv.appendChild(buttondiv.firstElementChild.cloneNode(true));

But maybe we need to wrap it such that it only runs once the DOM fully loads, like suggested here:

window.addEventListener("DOMContentLoaded", function(){
    var buttondiv = document.getElementsByClassName("board-btns")[0];
    if (buttondiv) {
        console.log("Button div found");
        buttondiv.appendChild(buttondiv.firstElementChild.cloneNode(true));
    } else {
        console.log("Button div not found");
    }
})

Again, does not work. Interestingly, neither of the console messages pop up either. So I tried the same wrap with the notification script (the part of the extension that did work) and it is suddenly broken. Conclusion being that the event listener never triggers. The website does load, by the way, I can interact with everything including the buttons, and with the aforementioned Ctrl+F business I can run the exact same code in the console and manually make everything do what I want it to do.

Next try is to implement a mutation observer. I am not super familiar with JS so I asked copilot and it gave me this code:

// Function to execute when mutations are observed
var callback = function(mutationsList, observer) {
    // Look through all mutations that just occured
    for(let mutation of mutationsList) {
        // If the addedNodes property has one or more nodes
        if(mutation.addedNodes.length) {
            var buttondiv = document.getElementsByClassName("board-btns")[0];
            if(buttondiv) {
                buttondiv.appendChild(buttondiv.firstElementChild.cloneNode(true));
                observer.disconnect(); // Stop observing
                return;
            }
        }
    }
};

// Create an observer instance linked to the callback function
var observer = new MutationObserver(callback);

// Start observing the document with the configured parameters
observer.observe(document, { childList: true, subtree: true });

Again, doesn’t work. Only other recommended solution I could find was to introduce an interval:

function checkForElement() {
    var buttondiv = document.getElementsByClassName("board-btns")[0];
    if(buttondiv) {
        buttondiv.appendChild(buttondiv.firstElementChild.cloneNode(true));
        return true; 
    }
    return false; 
}

var intervalId = setInterval(function() {
    console.log("Checking for element");
    if(checkForElement()) {
        clearInterval(intervalId); 
    }
}, 500);

All this does is print the console message every 500ms, but nothing actually works. I tried again to find the element manually with Ctrl+F in inspect element, I ran the code in the console, it made everything work, but the “checking for element” message kept coming up.

I feel like I have tried everything in my power, and even copilot gave up on me and ran out of further recommendations. If you have any suggestions, I am on my knees.

Modal window not opening as expected in Laravel Blade template

I have a Laravel Blade template where I’m trying to open a modal window on button click using jQuery. However, the modal window is not opening as expected, and I’m facing difficulties in identifying the issue.

 @foreach($uniqueCountries as $country)
        <div class="country-block" id="{{ $country }}">
            <h2>{{ $country }}</h2>
            <div class="company-cards">
                @php
                    $uniqueCompanies = $project->where('country', $country)->pluck('company')->unique();
                @endphp
    
                @foreach($uniqueCompanies as $companyIndex => $company)
                    <div class="card company-card" data-country="{{ $country }}" data-company="{{ $company }}">
                        <!-- Ваш код для компаній -->
                        <div class="card-body">
                            <h5 class="card-title">{{ $company }}</h5>
                            <p class="card-text">{{ $project->where('country', $country)->where('company', $company)->first()->city }}</p>
                            <button class="btn btn-secondary details-btn" data-index="{{ $companyIndex }}">Детальніше</button>
                        </div>
                    </div>
                @endforeach
            </div>
                <hr>
            <div class="vacancy-cards" id="{{ $country }}" style="display: none;">
                @foreach($uniqueCompanies as $companyIndex => $company)
                    @php
                        $vacancies = $project->where('country', $country)->where('company', $company);
                    @endphp
    
                    @foreach($vacancies as $vacancyIndex => $vacancy)
                        <div class="card vacancy-card" style="margin: 10px; display: none;" data-index="{{ $companyIndex }}">
                            <!-- Ваш код для вакансій -->
                            <div class="card-body">
                                <h5 class="card-title">{{ $vacancy->vacancy }}</h5>
                                <p class="card-text">{{ $vacancy->job }}</p> 
                                <button type="button" class="btn btn-primary details-btn" onclick="openPDFEditor('{{ $vacancy->id }}')">Створити PDF</button>

                                <div id="pdfEditorModal_{{ $vacancy->id }}" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #fff; padding: 20px; border: 1px solid #ccc; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); z-index: 9999; ">
                                    <div style="display: flex; justify-content: space-between; align-items: center;">
                                        <h3>Редактор PDF</h3>
                                        <button type="button" onclick="closePDFEditor('{{ $vacancy->id }}')">✖</button>
                                    </div>
                                    <hr>
                                    <div style="display: flex;" >
                                        <div id="pdfPreview" style="margin-right: 20px; border: 1px solid #ccc;  "></div>
                                        <div>
                                            <label for="name">Країна</label>
                                            <input type="text" id="name" required>
                                            <br>
                                            <label for="email">Назва проектузаводу</label>
                                            <input type="email" id="email" required>
                                            <br>
                                            <button type="button" onclick="generateAndPreviewPDF()">Створити і Переглянути PDF</button>
                                            <br> <br>
                                            <button type="button" onclick="downloadPDF()">Завантажити PDF</button>
                                        </div>
                                    </div>
                                </div>
                            </div> 
                        </div>
                    @endforeach
                @endforeach
            </div>
        </div>
    @endforeach
    
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
        $(document).ready(function(){
            // Ховаємо всі блоки крім блоків країн
            $('.country-block, .company-cards, .vacancy-card, .details-btn').hide();
    
            $(document).on('click', '.country-card', function(){
                var countryId = $(this).data('country');
    
                // Ховаємо всі блоки крім блоків компаній в обраній країні
                $('.country-block').hide();
                $('#'+countryId).show();
    
                // Ховаємо всі блоки крім блоків компаній
                $('.company-cards').hide();
    
                // Показуємо блок компаній в обраній країні
                $('#'+countryId+' .company-cards').show();
    
                // Ховаємо всі блоки вакансій та кнопок "Детальніше"
                $('.details-btn').show();
            });
    
            $(document).on('click', '.details-btn', function(){
                var index = $(this).data('index');
    
                // Ховаємо всі блоки вакансій
                $('.vacancy-card').hide();
    
                // Показуємо вакансії для обраної компанії
                $('.vacancy-card[data-index="'+index+'"]').show();
            }); 
            
        });
   
        function openPDFEditor(vacancyId) {
    document.getElementById('pdfEditorModal_' + vacancyId).style.display = 'block';
}

function closePDFEditor(vacancyId) {
    document.getElementById('pdfEditorModal_' + vacancyId).style.display = 'none';
}
    

I’ve ensured that jQuery is properly included before my custom script, and I’ve checked the syntax for any errors. Despite these precautions, the modal window doesn’t display.

How can you get the remaining eligible autocomplete items in a Select that uses a Datalist?

Say you have a datalist, like so:

<body>
<input id = "zip" list="zipcodes" name="zip" oninput="validate()">
<datalist id="zipcodes">
    <option value=11011>
    <option value=11342>
    <option value=12121>
    <option value=15453>
    <option value=10001>
</datalist>
</body>

And you want to access the remaining eligible autocomplete items as the user types, something like this:

function validate () {
    let items = document.getElementById("zip").magicalMethodThatReturnsDatalistOptions();
    console.log("Remaining items: " + items.join(", ");
}

For example, with the list above, if the user types in “11” the console will log:

Remaining items: 11011, 11342

Is there a simple way to do this?

Now, I could write the oninput function to compare the current value with an array of options – but I’m hoping to just access this data directly since it’s obviously already being done. Surely somewhere there’s an array that contains this information.

A non-serializable value was detected in an action, in the path: `register`. Value

The console is indicating this error:*A non-serializable value was detected in an action, in the path: register. Value: ƒ register(key) {
pStore.dispatch({
type: constants__WEBPACK_IMPORTED_MODULE_0
.REGISTER,
key: key
});
}
Take a look at the logic that dispatched this action: {type: ‘persist/PERSIST’, register: ƒ, rehydrate: ƒ}
reduxPersist.JS

import storage from 'redux-persist/lib/storage';
import { persistReducer } from 'redux-persist';

export default (reducers) => {
  const persistedReducers = persistReducer(
    {
      key: 'REACT-BASE',
      storage,
      whitelist: ['exempleReducer'],
    },
    reducers
  );

  return persistedReducers;
};

index.js

import { persistStore } from 'redux-persist';
import { configureStore } from '@reduxjs/toolkit';
import createSagaMiddleware from 'redux-saga';
import rootReducer from './modules/rootReducer';
import rootSaga from './modules/rootSaga';

import persistedReducers from './modules/reduxPersist';

const persistedReducer = persistedReducers(rootReducer);
const sagaMiddleware = createSagaMiddleware();

const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(sagaMiddleware),
});

sagaMiddleware.run(rootSaga);

export const persistor = persistStore(store);
export default store;

Google maps marker.AdvancedMarkerElement is Undefined

I’m trying to migrate from the legacy Marker map element to the newer AdvancedMarkerElement following this documentation, but I’m finding marker.AdvancedMarkerElement is undefined:

https://developers.google.com/maps/documentation/javascript/advanced-markers/migration

I’ve updated the script tag loading the libraries based on information here to include the (advanced)marker library:
https://developers.google.com/maps/documentation/javascript/libraries

This is now:
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=xxxxxx&libraries=places,marker,drawing,geometry&loading=async&callback=onMapsLoaded"></script>

The map initialization was updated to include an id, not that this seems to be relevant;

let pos = { lat: -25.344, lng: 131.031 };
_map = new google.maps.Map(
    $('#dMap')[0], { 
        zoom: 4, 
        center: pos, 
        mapTypeId: google.maps.MapTypeId.SATELLITE, 
        mapId: 'DEMO_MAP_ID' 
    }
);

According to the documentation I should be changing google.maps.Marker to google.maps.marker.AdvancedMarkerElement.

let pos = { lat: -25.344, lng: 131.031 };
let marker = new google.maps.marker.AdvancedMarkerElement({
    position: pos,
    map: _map,
    title: 'Some title'
});

Attempting to create a new marker this way fails however, since google.maps.marker is undefined, as obviously is google.maps.marker.AdvancedMarkerElement.

Anyone have any idea why this is?

I’ve looked at the script returned, and it has mentions to AdvancedMarkerElement throughout it, so I’d assume that the library is in fact loaded.

Is there some reference that I’m missing? Do I need to define or call some function so that google.maps.marker is defined? And if so, how?

Thanks in advance.

How to validate full iso date time string with Luxon

So what I want is to get a validation error for this iso string:

const dateTime = luxon.DateTime.fromISO("3");
console.log(dateTime.isValid); //true

I can’t understand why this is a valid ISO date?
Can I accept only this format to be valid:

const dateTime = luxon.DateTime.fromISO("2024-03-04T19:44:41.206Z");
console.log(dateTime.isValid); //true

Thank you!

event.target.id only questioning first option [duplicate]

I have code that uses event.target.id to evaluate what button is being pressed on the screen. But only the first one works. Even when I try to switch them around and still only my first button works.

var multiplyer = 1
var carparts = 0;
document.getElementById("karpartz").innerHTML = carparts;

function onButtonClick(event) {
  if (event.target.id === "car") {
    carparts = carparts + multiplyer;
    document.getElementById("karpartz").innerHTML = carparts;
  } else if (event.target.id === "clickpower") {
    console.log("test (clickpower)")
  } else if (event.target.id === "idle") {
    console.log("test (idle scrapping)")
  }
}
const button = document.querySelector('button');
button.addEventListener('click', onButtonClick);
<button id="car">
  car goes here soon
</button>
<button id="clickpower">
  upgrade click power
</button>
<button id="idle">
  upgrade idle scrapping
</button>

<p id="karpartz"></p>

<html>

  <body>
    <h1>
      <script type="text/javascript">
        document.getElementById("karpartz").innerHTML

      </script>
    </h1>
  </body>

</html>

I am not sure why this is happening. I think I might be doing something wrong or surpassing the limitations of JavaScript I am not sure. Please help.

handleSubmitR for a register component in REACT, BUT any code i write after axios.post won t work

so i have this function called handleSubmitR for a register component in react, and everytime i try to make a new account, the informations are sent to backend with no problems and inserted in database BUT any code i write after axios.post won t work.

In this case, after succesfully sent the data to local http://localhost:3002/users, the alert won t appear, neither the catch block won t be called for there is no error.

const handleSubmitR = async (values) => {
    try {
      await axios.post("http://localhost:3002/users", values);
      
      alert("yess");
      
    } catch (err) {
      if (err.response.status === 401) {
        alert("error");
        
      }
    }
    
  };

also the backend:

export const create = (user: User, callback: Function) => {
  
  const sql = "SELECT * FROM users WHERE email = ?";
  
  db.query(sql, [user.email], (err, result) => {
    const row = (<RowDataPacket>result)[0];
    if (row !== null && row !== undefined) {
      callback("User already exists!." + err?.message);
    } else {
      const queryString =
        "INSERT INTO users (nume, prenume, email, parola) VALUES (?, ?, ?, ?)";
      console.log("insert",user);
      
      let saltRounds = bcryptjs.genSaltSync(10);
      let password_hash = bcryptjs.hashSync(user.parola!, saltRounds);
      try {
        db.query(
          queryString,
          [user.nume, user.prenume, user.email, password_hash],
          (err, result) => {
            if (<OkPacket>result !== undefined) {
              
              const insertId = (<OkPacket>result).insertId;
              callback(null, insertId);
            } else {
              
              console.log("error email", err);
              //callback(err, 0);
            }
          }
        );
      } catch (error) {
        callback(error);
      }
    }
  });
};

i did includes the register in the context file for authentification but there was some kind of delay?
like if i submit the form, everything works with no error, still no alert but if i pess the submit button second time, i get an error from the register function(like the email already exist in the db) and after the eror i get the alert

context:

const register = async (input) =>{
        try {
            const res = await axios.post(configData.SERVER_URL , input);
            
        } catch (err) {
            throw err; 
        }
    }

register component:

const handleSubmitR = async (values) => {
    try {
     await register(formData)
      
      alert("yess");
      
    } catch (err) {
      if (err.response.status === 401) {
        alert("error");
        
      }
    }
   
  }

Javascript, Conosole.log displaying empty variable after running an async function

I am trying to save values into a useState array and then display them on the local webpages’ console however after running the async function the values I am inputting doesn’t seem to appear. Can anyone explain why?


const shouldLog2 = useRef(true);
const [trueArray, setTrueArray] = useState([]);

async function currentAuthenticatedUser() {
  try {
    const { username, userId, signInDetails } = await getCurrentUser();
    //const username = 'person_a'; //Unhashing this line can be used as a substitute for the line above
    

    //This correctly displays the currently logged in user.
    console.log(`The username: ${username}`);

    await userVerification(username);

    //Displays an empty array 
    console.log(`Final Output: ${trueArray}`);

  } catch (err) {
    console.log(err);
  }
}


async function userVerification(username){

  (setTrueArray((TrueArray) => [
    ...TrueArray,
    username,
  ]));
}

useEffect(() => {
  if(shouldLog2.current){
    shouldLog2.current = false;
    currentAuthenticatedUser();
  }
}, [shouldLog2]);

Since, I am working with async functions I’ve tried using await statements like shown in the code above but that doesn’t seem to have made an impact. Any help would be appreciated.

Experiencing random freezes on React Native Android Screen that uses WebSockets and gets updated frequently

What’s on the screen

I am building an app that allows the users to place live bids on items through WebSockets and the highest bidder gets the item.

The screen is simple, it has cards of items that displays the item information like images and information like price etc, and it changes after every items gets sold to certain user.

A bid window where the current highest bid and bidder gets displayed and the timer is going on to show the seconds left before this item gets sold.

A list of users with the items they have bought so far and the next items in queue.

A toggle that allows users to place bids automatically

Few Buttons from where the user can place bids on the items.

Note: All these are separate components, but the state updates has to be done on the parent component only to share the updates across all the child components, since the requirement is such.

Problem

Note: Problem only occurs in build APK made from .gradlew assembleRelease not in Development Build.

When the users are placing bids on items very frequently, its like 2 to 3 bids per second, after 15 to 18 items the entire screen gets frozen, no state updates, no UI changes, just stuck, only the toggle, side menu (which I drag from left) and the scrolling related things work on the screen. Ultimately the user has to close the app (using Home Button) remove the app from recent and re-run the application.

This is happening very randomly, on some devices it happens very quickly (4 to 5 items) but in some devices like it takes 17 to 18 items to freeze.

Patterns

There are some patterns to this problem which we have experienced.

  • When the users bid very aggressively the app freezes more quicker.
  • When there are more number of users bidding, it freezes.
  • The devices we have used ranged from 2GB RAM to 8GB RAM, but the strange thing to notice is that in 2GB device the app didn’t freeze that quickly and sometimes didn’t freeze at all, however in 6GB and 8GB phones it froze more quicker.

Logic

The logic is simple, when the user gets into the auction, the network request goes out asking the connection from the socket server that is actually conducting the auction, when the connection is established, the client is provided with the snapshot of the auction that contains the current information of the auction like the amount, current Bidder etc. The client side code then updates it’s UI states accordingly. Now the user is connected with the socket and getting live updates about the auction through different events and all this happens within a useEffect without any dependency.

When the user bids, the bid event is sent to the server, the server then broadcast that message to other connected users saying “that this user just placed a bid of this amount on this item” and all users update their states accordingly including the bidder.

{
  "name": "--",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "lint": "eslint .",
    "start": "react-native start",
    "test": "jest"
  },
  "dependencies": {
    "@notifee/react-native": "^7.8.2",
    "@react-native-async-storage/async-storage": "^1.21.0",
    "@react-native-community/clipboard": "^1.5.1",
    "@react-native-firebase/app": "^18.7.3",
    "@react-native-firebase/auth": "^18.7.3",
    "@react-native-firebase/messaging": "^18.7.3",
    "@react-navigation/bottom-tabs": "^6.5.11",
    "@react-navigation/drawer": "^6.6.6",
    "@react-navigation/material-top-tabs": "^6.6.5",
    "@react-navigation/native": "^6.1.9",
    "@react-navigation/native-stack": "^6.9.16",
    "@reduxjs/toolkit": "^1.9.7",
    "base-64": "^1.0.0",
    "date-fns": "^2.30.0",
    "jwt-decode": "^4.0.0",
    "lodash": "^4.17.21",
    "lottie-react-native": "^6.4.0",
    "react": "18.2.0",
    "react-hook-form": "^7.47.0",
    "react-native": "0.72.6",
    "react-native-animatable": "^1.4.0",
    "react-native-bootsplash": "^5.1.3",
    "react-native-confirmation-code-field": "^7.3.2",
    "react-native-countdown-circle-timer": "^3.2.1",
    "react-native-date-picker": "^4.3.5",
    "react-native-dotenv": "^3.4.9",
    "react-native-gesture-handler": "^2.13.4",
    "react-native-image-picker": "^7.1.0",
    "react-native-linear-gradient": "^2.8.3",
    "react-native-modal": "^13.0.1",
    "react-native-pager-view": "^6.2.3",
    "react-native-progress": "^5.0.1",
    "react-native-reanimated": "^3.5.4",
    "react-native-safe-area-context": "^4.7.4",
    "react-native-screens": "^3.27.0",
    "react-native-sound": "^0.11.2",
    "react-native-sse": "^1.2.0",
    "react-native-svg": "^13.14.0",
    "react-native-svg-transformer": "^1.1.0",
    "react-native-swipe-gestures": "^1.0.5",
    "react-native-switch-selector": "^2.3.0",
    "react-native-tab-view": "^3.5.2",
    "react-native-toast-message": "^2.2.0",
    "react-native-uuid": "^2.0.1",
    "react-redux": "^8.1.3",
    "react-timer-hook": "^3.0.7",
    "redux-logger": "^3.0.6"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0",
    "@babel/preset-env": "^7.20.0",
    "@babel/runtime": "^7.20.0",
    "@react-native/eslint-config": "^0.72.2",
    "@react-native/metro-config": "^0.72.11",
    "@tsconfig/react-native": "^3.0.0",
    "@types/react": "^18.0.24",
    "@types/react-test-renderer": "^18.0.0",
    "babel-jest": "^29.2.1",
    "babel-plugin-transform-remove-console": "^6.9.4",
    "eslint": "^8.19.0",
    "jest": "^29.2.1",
    "metro-react-native-babel-preset": "0.76.8",
    "prettier": "^2.4.1",
    "react-test-renderer": "18.2.0",
    "rn-nodeify": "github:tradle/rn-nodeify",
    "typescript": "4.8.4"
  },
  "engines": {
    "node": ">=16"
  }
}

This is the list of things we have tried to solve this issue, none is successful.

Check for Memory Leaks

I ran profiling on Android Studio of Development Build to check for the memory leak and see if the memory is going really up. I saw that the memory never went above 300mb and the entire auction of 30 items ended. It was just for checking for any memory leaks, I always knew the app will never freeze on Development it only freezes on release APK.

I did profiling on both Emulator and the physical device.

Enabled Hermes Engine

It didn’t work too

Tried newArchEnabled

Tried enabling new architecture and made all CMakeList changes, but then the app didn’t open at all, my Android12 showed the “This app contains bugs” message on release build and the development build kept crashing.

Enhanced code with removeEventListeners and other fixes related to state updates

I minimized the state updates and removed any unnecessary setStates, brought the related variables inside useRefs, removed eventListeners in the clean up functions.

CPU Threads in Profiling

I checked the CPU main thread and UI Thread for any unnecessary activity and found out that it only works when it is required to, like it show the activity only on events where the state is actually updated.

How to keep a table row always the last row?

I have a table which shows items, and a row that shows the total of the item prices, and a button to add a row. how to keep the total table always in the bottom

<table id="expense-table">
                <tr id="table_main">
                    <td>Item</td>
                    <td>Unit price</td>
                    <td>Quantity</td>
                    <td>Total</td>
                </tr>
                <tr>
                    <td>
                        <select>
                            <option value="">Select Item</option>
                            <option value="1">Item 1</option>
                            <option value="2">Item 2</option>
                        </select>
                    </td>
                    <td>
                        <input type="number" class="unit-price">
                    </td>
                    <td>
                        <input type="number" class="quantity">
                    </td>
                    <td>
                        <input type="text" class="total" value="0" disabled>
                    </td>
                </tr>
                <tr id="total">
                    <td id="total-table" colspan="3">
                        Total
                    </td>
                    <td id="total-result">
                        0
                    </td>
                </tr>
            </table>
            <button id="add-btn">Add Item</button>

addButtonEl.addEventListener("click", addExpenseInput);

Is there a VS Code extension to strip JS objects and arrays of whitespaces and carriage returns?

I am looking for a VS Code extension that allows me to select with the mouse a block of JS code like this:

{
  qwe: 123,
  asd: 'qwe',
  dfg: true,
}

and format it like this:

{qwe:123,asd:'qwe',dfg:true}

Another example. Select this block with the mouse:

[
  'qwe',
  true,
  123,
]

right click on the selection, choose “format me” from the VS Code menu. The code becomes:

['qwe',true,123]

Is there an extension that does this?

Could I do it by configuring a linter?

XMLHttpRequest readyState DONE excutes more than once

I have the following method:

function sendHttpRequest(resource, method, body, successCallback, failCallback) {
  var xhr = new XMLHttpRequest();
  var url = this.BASE_URL + resource;

  xhr.open(method, url, true);

  showSpinner();
  xhr.onreadystatechange = function() {
    if (xhr.readyState == XMLHttpRequest.DONE) {
        if (xhr.status == 200) { 
          successCallback(xhr.response); 
          hideSpinner();
        }  
        if (xhr.status != 200) {  
          hideSpinner();
          failCallback(xhr.status);
        }
    }
  };
  xhr.send(body);  
}

I know that xhr.onreadystatechange is called several times, but I can’t understand why it is included more than once in “if (xhr.readyState == XMLHttpRequest.DONE)”.

FailCallback and SuccessCallback are executed at the same time.

I would like to understand why this happens.

I thank you in advance

I try set

xhr.open(method, url, fase);

and

if (xhr.readyState == XMLHttpRequest.DONE) {
    xhr.onreadystatechange = null;

Do you need to import a module into a module if it isn’t used in the template?

I was wondering, if I have a component in Module2, and I use the component in Module1, do I need to import Module2 into Module1 if it is only going to be used in the typescript and not the template, for example as a @ContentChild(Component2) component2 like in the example below:

This uses the directive from Module2 in the typescript but not the template:

@NgModule({
  imports: [Module2], // Is this even needed as it's not used in the template
  declarations: [Component1]
})
export class Module1 {}

@Component({
  selector: 'example',
  template: `<ng-container [ngTemplateOutlet]="tpl" />`
})
export class Component1 {
  @ContentChild(Directive2, {read: TemplateRef}) tpl: TemplateRef<unknown>;
}

Example dependency for Module.

@NgModule({
  declarations: [Component2]
})
export class Module2 {}

@Directive({
  selector: '[my-directive]'
})
export class Directive2 {}

Within the Application (imports both Module1 and Module2 in AppModule):

<example>
  <ng-template my-directive>
    Hello World
  </ng-template>
</example>