javascripthtmlreactjsloading

Function triggered before site is finished loading fully


My function is triggered before my site is fully loaded so my filterBar returns a NULL. This is my html project that i am trying to convert into and react.

My function

document.addEventListener('DOMContentLoaded', function() {
  var filterBar = document.getElementById("filterBar");
  var btnFilterSve = document.querySelector('.btnFilterSve');
  btnFilterSve.style.backgroundColor = '#7c0001';
  btnFilterSve.style.color ='#fff';

  filterBar.addEventListener('click', function(event) {
    console.log('Clicked element:', event.target);
    var selectedFilter = event.target.id;

    if (event.target.tagName === 'BUTTON') {
      console.log('Button clicked:', event.target);

      var cButtons = filterBar.querySelectorAll('button');

      cButtons.forEach(function(cButton) {
        cButton.style.backgroundColor = '#D9D9D9';
        cButton.style.color = '#696969';
      });

      event.target.style.backgroundColor = '#7c0001';
      event.target.style.color = '#fff'; 
    }
  });
  
});

This is my index.js file both of my scripts are deferred PageSwitch works even without defer but Filter loads before everything

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,
    initial-scale=1.0">
    <title>Your Title Here</title>
    <link rel="stylesheet" href="/assets/src/main/view/MainPageLooks.css">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inria+Sans:wght@400;700&display=swap" rel="stylesheet">
    <script src="/assets/src/main/controller/PageSwitch.js" defer></script>
    <script src="/assets/src/main/controller/Filter.js" defer></script>

  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

*Edit my jsx file 

    ```
    import React from "react";
    class MainPage extends React.Component {
    switchMidSection = () => {...}
    
    render() {
     return (
     <div>
      <div className="topContainer">
        <div className="topContainerHorizontal"> 
         <button type="button" className="btnLogo">
          <img src="/assets/images/logoOut.png" border="0"height="80" width="80"/>
           </button>
             <input type="text" placeholder="Pretraži kafiće, 
               barove,pabove..." className="searchContainer"/>
                <button 
                   type="button"className="btnSearch">Pretraga</button>
                
                <button className="btnJezik">
                    <img src="/assets/images/globe.png"/>
                    <h1 className="tekstJezik">
                        <img src="/assets/images/srb.png"/>
                    </h1>
                </button>
    
        {/*some code*/}
    
       <div className="filterBar" id="filterBar">
        <button type="button" className="btnFilterSve"id="btnFilterSve">
               Sve
              </button>
         <button type="button" className="btnFilterKlub">
               Klub
          </button>
         <button type="button" className="btnFilterRestoran">
                Restoran
          </button>
         <button type="button" className="btnFilterBar">
                Bar
          </button>
         <button type="button" className="btnFilterPab">
                Pab
          </button>
         <button type="button" className="btnFilterKafa">
                 Kafa
         </button>
         <button type="button" className="btnFilterSplav">
                Splav
         </button>
         <button type="button" className="btnFilterOstalo">
                  ...
          </button>
       </div>
    {/*some code*/}

Error i get

Button btnFilterSve not found!

Filter.js:13 Uncaught TypeError: Cannot read properties of null (reading 'addEventListener') at HTMLDocument. (Filter.js:13:15)

I have tried chat.gpt but it doesnt help at, also this is my first time using react so complicated tutorials didnt help.


Solution

  • I think you may be misunderstanding the sequence of events that are happening. From what I can tell, this is what is occurring:

    Once the JS files load in, the browser will attempt to execute the code contained within it. You're event listener is added here. At the the point that remaining assets resolve, DOMContentLoaded is fired. Based on the error you're reporting, this is happening before the Filter JS has fully executed, (assuming you have some code in Filter which renders your #filterBar element.

    This is the key, DOMContentLoaded isn't waiting for your JS to evaluate, it's waiting for the document + assets to finish loading on the page. You're going to need some other event that trigger's your "attach events and style #filterBar" function. One naïve way to accomplish this is to introduce a timeout function that tests if the #filterBar element is present first, then executing the "attach events and style function" like this:

    function maybeAttachEventsAndStyle() {
       const filterBar = document.getElementById("filterBar");
       if (!filterBar) {
          setTimeout(maybeAttachEventsAndStyle, 500); // wait .5 seconds and try again
          return;
       }
       // attach events and styles
    }
    
    // Setup the initial event
    document.addEventListener('DOMContentLoaded', maybeAttachEventsAndStyle);
    

    This approach works, but you're always going to wait extra time than is absolutely needed. A better approach might be to have each file fire a 'ready' event that your main function can use to determine when it's ready to post process your elements.

    --- Edit ---

    I see from your other comments that you're using React. Instead of attempting to post process your elements, its better to use React's API to assign your events and styles. That way you can ensure they render correctly the first time. Here is an example FilterBar component --

    function FilterBar() {
      const filters = [{ name: "filter a" }, { name: "filter b" }];
    
      return (
        <div style={{backgroundColor: "green"}}>
          {filters.map((filter) => (
            <button key={filter.name} onClick={() => console.log(filter.name)} style={{backgroundColor: "white", color: "black"}}>
              {filter.name}
            </button>
          ))}
        </div>
      );
    }
    

    I'm making some assumptions about your code, but this example will render a list of potential filters as buttons and attach click events to each of them.