Adding a dropdown feature in a React.js component

I am working on a React project that requires me to create a dropdown menu using only pure React. The dropdown should also be responsive for different devices.

The expected interactions are as follows:

  • For larger devices like desktop screens: On hover
  • For smaller devices like mobile phones: On click

Working Example

function App() {

  React.useEffect(() => {

    const has_submenu = document.querySelector(".has-submenu");
    const submenu = document.querySelector(".submenu");
    const submenu_height = submenu && submenu.childElementCount * 34;

    if (has_submenu && submenu && submenu_height) {
      has_submenu.addEventListener("mouseover", function () {
        submenu.style.height = submenu_height + "px";
      });

      has_submenu.addEventListener("mouseout", function () {
        submenu.style.height = "0px";
      });

      submenu.addEventListener("mouseover", function () {
        submenu.style.height = submenu_height + "px";
      });

      submenu.addEventListener("mouseout", function () {
        submenu.style.height = "0px";
      });

    }

  }, []);

  return (
    <nav>
      <ul>
        <li className="menu-item  has-submenu inline-flex"> Account </li>
           <ul className="submenu">
              <li className="submenu-item submenu1">
                Profile
               </li>
              <li className="submenu-item submenu1">
                Change Password
               </li>
           </ul>
       </ul>
    </nav>
  );
}

ReactDOM.render(<App />, document.querySelector('#app'));
.submenu {
  background: #fff;
  position: absolute;
  list-style-type: none;
  padding: 0;
  height: 0px;
  overflow: hidden;
  color: #000;
  cursor: pointer;
  transition: height 0.33333s ease-in;
}
.submenu-item {
  padding: 2px 16px;
  list-style-position: outside;
  transition: background 0.33333s;
}
<script src="https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="6e1c0b0f0d1a2e5f584059405e430f021e060f405e">[email protected]</a>/umd/react.development.js"></script>
<script src="https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d6a4b3b7b5a2fbb2b9bb96e7e0f8e1f8e6fbb7baa6beb7f8e6">[email protected]</a>/umd/react-dom.development.js"></script>

<div id="app"></div>

In the code snippet above, you can see that the submenus are displayed when hovering over the 'Account' text.

Requirement:

Although this approach works, I believe it's relying too much on direct DOM manipulation and not utilizing React efficiently. Is there a pure React way to achieve the same functionality?

If so, I would appreciate any guidance on refactoring the code to implement the dropdown menus for both desktop (hover) and mobile (click) views.

Expected Output:

Desktop: (On hover)

 Dashboard      Account               Logout
                | -- Profile -- |
                | -- Change Password -- |

Mobile: (On click)

  Dashboard

  Account 

    | -- Profile -- |
    | -- Change Password -- |

 Logout

Answer №1

React grants you the power to manage state within your components. There are numerous approaches to handle this, but I'll provide a brief example below.

You should respond to events and modify the state. Whenever the state changes, your component will undergo re-rendering.

const MAGIC_NUMBER = 34;

const MyApp = () => {

  const subMenuRef = React.createRef();
  const [ isMenuOpen, setMenuOpen ] = React.useState(false);
  const [ menuHeight, setMenuHeight ] = React.useState(0);

  const openMenu = () => setMenuOpen(true);
  const closeMenu = () => setMenuOpen(false);

  React.useEffect(() => {
    if (!subMenuRef.current) { return; }
    setMenuHeight(subMenuRef.current.childElementCount * MAGIC_NUMBER);
  }, [subMenuRef.current]);

  return (
    <nav>
      <ul>
        <li
          className="menu-item inline-flex" 
          onMouseOver={openMenu}
          onMouseOut={closeMenu}
        >
         Account
        </li>
        <ul
          className="submenu"
          ref={subMenuRef}
          style={{height: isMenuOpen ? menuHeight : 0}}
          onMouseOver={openMenu}
          onMouseOut={closeMenu}
        >
          <li className="submenu-item submenu1">
           Profile
          </li>
          <li className="submenu-item submenu1">
           Change Password
          </li>
        </ul>
      </ul>
    </nav>
  );
};

ReactDOM.render(<MyApp />, document.querySelector('#app'));
.submenu {
  background: #fff;
  position: absolute;
  list-style-type: none;
  padding: 0;
  height: 0px;
  overflow: hidden;
  color: #000;
  cursor: pointer;
  transition: height 0.33333s ease-in;
  border: 1px solid #777;
}
.submenu-item {
  padding: 2px 16px;
  list-style-position: outside;
  transition: background 0.33333s;
}
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>

<div id="app"></div>

There's quite a bit happening above, so let me simplify it here...

  • We directly added event listeners to our elements.
    • With React, we can easily attach these event listeners without needing to access the DOM.
  • We introduced some state to track the open/close state of the menu.
    • In our event handlers, we simply toggle the state for things to function correctly.
    • We incorporate conditional logic in our elements to manage the state change (altering the height when the menu opens/closes).
  • We utilized a ref to directly access the DOM in order to obtain the child element count. Documentation
    • If you're fully in React, these children are likely part of a list that you're iterating over, hence you could easily get the length from there.
    • In general, tapping into the underlying DOM isn't necessary as most actions will be handled internally by React itself.

PS: Try to avoid using magic numbers whenever feasible. In this scenario, you have a value of 34. What does this signify? Why is it included? If precise heights are necessitated, rely on css computations using rem or lineHeight

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The value entered in the HTML input is not being recognized by PHP

I'm facing an issue with my project (index.php), which is a HTML webpage. I am trying to use the 'include' function to bring in the header and footer (header.php, footer.php). Additionally, I want the current date to automatically populate i ...

Tips for invoking a Laravel Model function with a parameter in a Vue.js template

I'm facing an issue with displaying stock quantities for products in a Vue.js template. I have a product table and stock table where data about product sale and purchase are stored. I need to calculate the stock of each product based on purchase and s ...

Centering divs using iPad media queries does not seem to work properly

While working on my website, I encountered an issue with displaying content properly on various mobile devices. I have implemented media queries for this purpose. Currently, on the main site, two divs (#wrap and #scrollbar) are positioned next to each oth ...

Converting an HTML form with empty values into JSON using JavaScript and formatting it

While searching for an answer to my question, I noticed that similar questions have been asked before but none provided the solution I need. My situation involves a basic form with a submit button. <form id="myForm" class="vertically-centered"> ...

What is the best way to define the width of a value in percentage using JavaScript's

Here's a snippet of code that I'm working with: <div id="test-content"></div> <script> var width = 100; document.getElementById('test-content').style.width = width+ '%'; </script> I encountered an iss ...

How to apply background images to LI elements using CSS

I've been experimenting with CSS-generated tabs to display an active state of an arrow underneath the tab. I tried using the background position properties to position the image for the hover event, but it would extend beyond the predefined boundaries ...

What is the best way to incorporate right side padding into a horizontal scroll?

I'm attempting to include padding on the right side of a horizontal scroll so that the last item in the scroll appears in the center of the screen. Here's what I have tried so far. While the left padding works perfectly, I'm puzzled as to w ...

Issues with utilizing jQuery AJAX for form submissions

I am currently customizing a webpage to fit the specific requirements of a client using a template. The page contains two contact forms which are being validated and sent to a PHP file via AJAX. One of the forms is standard, while the other one has been mo ...

Sending data from Javascript to PHP using Ajax in Wordpress

Is there a way to pass a JavaScript Object to PHP using Ajax in a Wordpress environment? Currently, the code snippet below is only returning 0 instead of the object. What adjustments should be made to successfully use the amount value in the PHP script? ...

"Exploring three.js: Transforming a plane's orthogonal vector into a rotation matrix for the plane

I am trying to set the rotation of a plane by using three numbers representing the rotation in radians along the x, y, and z axes. Although I do not have these numbers, I do have a vector called 'myVec' which should be orthogonal to the plane af ...

`Can you guide me on transferring an audio blob from javascript to python?`

My goal is to transfer an audio blob from JavaScript to a Python script running on the server. Below is a snippet of my AJAX code in JavaScript: var fileType = 'audio'; var fileName = 'output.wav'; var formData = new FormData(); formDa ...

Similar to Laravel's service providers or WordPress style plugins, Node.js has its own unique way of managing and extending functionality

Coming from a PHP/Laravel background, my team is considering using Node.js (and sails) for our upcoming project - a collaboration studio for scholars. However, before making the transition, I am curious about the best practices for creating Laravel-style s ...

Display JSON values on PHP event calendar unless they are sold out

I have been struggling for days to find the most effective approach to achieve my goal. I am extracting data from a JSON URL that is structured in an array format as shown below. The data spans from a month to a year, and I need to check if tickets are sol ...

Start running additional JavaScript code only after the previous one has been fully executed

Scenario: I am facing a situation where I have a web form that is submitted through the following event listener: $('#myForm').on('valid', function (e) { ... } Within this function, I have a code snippet that fetches the geo location ...

What is the most effective method for displaying two external web pages next to each other?

Looking for a solution to display an English Wikipedia article on the left side of the page alongside its Spanish version on the right side. Wondering if it's possible using HTML, JavaScript, AJAX, etc. I am aware that I could use iframes, but I woul ...

Retrieve the $$state value from the Service Function

I am new to Angular and struggling to understand a function in my service. I have this code snippet: checkRoomNameStatus: function() { var promises = []; var emptyRooms = []; DatabaseService.openDB().transaction(function(tx) { tx.exec ...

Trouble with parseJSON when handling form POST in Python

I'm struggling with a javascript HTML page that has an action POST to a python file, expecting a JSON response back. Despite my efforts, I can't figure out how to catch and parse the JSON data. The HTML and python code excerpts below should show ...

The React parent component experiences a delay of one step when updating checkbox states

In one component: import React, { Component } from "react"; import { Button } from "./Button"; export class Dashboard extends Component { constructor(props) { super(props); this.state = { numbers: [], disabled: false }; thi ...

Identifying content loading in ajax div

I am currently utilizing the ajaxpage feature from the code offered by Dynamic Drive (). My goal is to have the original page, which sent the ajax content request to the div, recognize when the div has finished loading. Here are the methods I have experi ...

What steps should I take to ensure that this accordion is responsive?

I am currently working on an accordion design and trying to figure out how to make it full width and responsive. I have experimented with various width and display attributes, but I haven't been able to achieve the desired result yet. If you'd l ...