ITACS
ITACS

Reputation: 93

TypeError: Cannot read properties of null (reading 'classList')

enter image description hereI am working in a nextjs application. I was just trying to make a dropdown.

This is my full code:

import React from 'react'
import Link from 'next/link'
import styles from './header.module.css'

const Header = () => {
    /* When the user clicks on the button,
toggle between hiding and showing the dropdown content */
    const myFunction =()=>{
        document.getElementById(styles.myDropdown).classList.toggle("show");
    }

    // Close the dropdown menu if the user clicks outside of it
    if (typeof window !== "undefined") {
        window.onclick = function (event) {
            if (!event.target.matches('.dropbtn')) {
                var dropdowns = document.getElementsByClassName("dropdown-content");
                var i;
                for (i = 0; i < dropdowns.length; i++) {
                    var openDropdown = dropdowns[i];
                    if (openDropdown.classList.contains('show')) {
                        openDropdown.classList.remove('show');
                    }
                }
            }
        }
      }

    return (
        <div className={styles.header}>
            <div className={styles.logoLink}>
                <img src="images/itacs.png" alt="" className={styles.logo} />
            </div>
            <div className={styles.services}>
                <ul>
                    <li><Link href="/page">Docs</Link></li>
                    <li><Link href="/page">Learn</Link></li>
                    <li><Link href="/page">Projects</Link></li>
                    <li><Link href="/page">Blog</Link></li>
                    <div className={styles.dropdown}>
                        <button onClick={myFunction} className={styles.dropbtn}>Dropdown</button>
                        <div id={styles.myDropdown} className={styles.dropdownContent}>
                            <a href="/">Link 1</a>
                            <a href="/">Link 2</a>
                            <a href="/">Link 3</a>
                        </div>
                    </div>
                </ul>
            </div>
            <form action="" className={styles.headerForm}>
                <a href="/" className={styles.logIn}>Log In</a>
                <a href="/" className={styles.getStarted}>Get Started</a>
            </form>
        </div>
    )
}

export default Header

Here I have just added the classlist in the id of div! I am trying to show the below div when the button is clicked as a dropdown menu. I am not able to figure this out!

For anyone who is wondering what is there present in css file :

/* dropdown */
/* Dropdown Button */
.dropbtn {
    background-color: #3498DB;
    color: white;
    padding: 16px;
    font-size: 16px;
    border: none;
    cursor: pointer;
  }
  
  /* Dropdown button on hover & focus */
  .dropbtn:hover, .dropbtn:focus {
    background-color: #2980B9;
  }
  
  /* The container <div> - needed to position the dropdown content */
  .dropdown {
    position: relative;
    display: inline-block;
  }
  
  /* Dropdown Content (Hidden by Default) */
  .dropdownContent {
    display: none;
    position: absolute;
    background-color: #f1f1f1;
    min-width: 160px;
    box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
    z-index: 1;
  }
  
  /* Links inside the dropdown */
  .dropdownContent a {
    color: black;
    padding: 12px 16px;
    text-decoration: none;
    display: block;
  }
  
  /* Change color of dropdown links on hover */
  .dropdownContent a:hover {background-color: #ddd}
  
  /* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */
  .show {display:block;}

Any Help would be appreciated!

Upvotes: 2

Views: 25753

Answers (3)

whygee
whygee

Reputation: 1984

It should be <div className="dropdown"> instead of <div class="dropdown"> and <div id={styles.myDropdown} className={styles.dropdownContent}>

You would also avoid doing vanilla js inside a react/next app.

You would instead have a react state like this:

const [dropdownToggled, toggleDropdown] = useState(false);
  
const handleClick = () => {
  toggleDropdown(!dropdownToggled);
};

And have a condition on whether your jsx have a className hidden that sets display: none

something like this:

     <div
      className={`${styles.dropdownContent} 
      ${dropdownToggled ? styles.hidden : ""}`}
     >
       <a href="/">Link 1</a>
       <a href="/">Link 2</a>
       <a href="/">Link 3</a>
     </div>

To make the dropdown close when the user clicks outside, you would have a div like this:

<div
  className={styles.backdrop}
  onClick={() => toggleDropdown(true)}
></div>

that is styled like this:

.backdrop {
  position: absolute;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: -1;
}

Now since this div takes the whole screen and is positioned absolute when the user clicks anywhere on the page the onClick will fire and toggle the dropdown.

Working CodeSandbox.

Upvotes: 5

ITACS
ITACS

Reputation: 93

import React, { useState } from 'react'
import Link from 'next/link'
import styles from './header.module.css'

const Header = () => {
    const [dropdownToggled, toggleDropdown] = useState(false);

    const handleClick = () => {
        toggleDropdown(!dropdownToggled);
        console.log("boy");
    };

    // Close the dropdown menu if the user clicks outside of it
    if (typeof window !== "undefined") {
        window.onclick = function (event) {
            if (!event.target.matches('#dropbtn')) {
                var dropdowns = document.getElementsByClassName(styles.dropdownContent);
                var i;
                for (i = 0; i < dropdowns.length; i++) {
                    var openDropdown = dropdowns[i];
                    if (openDropdown.classList.contains('show')) {
                        openDropdown.classList.remove('show');
                    }
                }
            }
        }
    }

    return (
        <div className={styles.header}>
            <div className={styles.logoLink}>
                <img src="images/itacs.png" alt="" className={styles.logo} />
            </div>
            <div className={styles.services}>
                <ul>
                    <li><Link href="/page">Docs</Link></li>
                    <li><Link href="/page">Learn</Link></li>
                    <li><Link href="/page">Projects</Link></li>
                    <li><Link href="/page">Blog</Link></li>
                    <div className={styles.dropdown}>
                        <button onClick={handleClick} id="dropbtn" className={styles.dropbtn}>Dropdown</button>
                        <div
                            className={`${styles.dropdownContent} ${dropdownToggled ? styles.show : ""}`}
                        >
                            <a href="/">Link 1</a>
                            <a href="/">Link 2</a>
                            <a href="/">Link 3</a>
                        </div>
                    </div>
                </ul>
            </div>
            <form action="" className={styles.headerForm}>
                <a href="/" className={styles.logIn}>Log In</a>
                <a href="/" className={styles.getStarted}>Get Started</a>
            </form>
        </div>
    )
}

export default Header

Yea finally configured it out. Thanks to whygee and Ingo Steinkie

Upvotes: 2

Ingo Steinke
Ingo Steinke

Reputation: 941

If you use variables to set the id and className in the markup, you should probably use the same variables in your script, like

document.getElementById(`${styles.myDropdown}`).classList.toggle("show");

[Edit] or without the unnecessary redundant template string:

document.getElementById(styles.myDropdown).classList.toggle("show");

Assuming that styles.myDropdown is a string (not an object).

Otherwise your code does not ensure that the id and className will be the same.

Upvotes: 1

Related Questions