javascriptreactjsbulma

is-active conditionally changed onClick


Bulma's navbar item class can take on attributes such as 'is-active', affecting changing the highlighting of the item. In a React app, I'd like a click event to change which item is selected.

A simpler problem is toggling display of the dropdown.

import React, { useState } from 'react';

const Dropdown = () => {
    const [active, setActive] = useState('');

    const toggleActive = (event) => {
        event.preventDefault();
        if (active=='is-active'){
            setActive('');
        } else {
            setActive('is-active');
        }
    };
    
    return (
    <div class={`dropdown ${active}`}>
        <div class="dropdown-trigger">
            <button class="button" 
                    aria-haspopup="true"
                     aria-controls="dropdown-menu"
                     onClick={toggleActive}>
                <span>Dropdown button</span>
                <span class="icon is-small">
                    <i class="fas fa-angle-down" aria-hidden="true"></i>
                </span>
            </button>
        </div>
        <div class="dropdown-menu" id="dropdown-menu" role="menu">
            <div class="dropdown-content">
                <a href="#" class="dropdown-item"> Dropdown item </a>
                <a class="dropdown-item"> Other dropdown item </a>
                <a href="#" class="dropdown-item is-active"> Active dropdown item </a> // should be dynamic! 
                <a href="#" class="dropdown-item"> Other dropdown item </a>
                <hr class="dropdown-divider" />
                <a href="#" class="dropdown-item"> With a divider </a>
            </div>
        </div>
    </div>
    )    
}

export default Dropdown;

Is there a way to do this without one useState creation for each menu item?

I'm wondering what is the best way to accomplish this- Perhaps an object, instead of a string, could be used as the output of useState, one element for each menu item. When a given menu item is toggled, the current one is deactivated and the new is activated.


Solution

  • You can use arrays with the items informations. Mapping it, you have an index and it can be used to compare with the current selected item index.

    The logic is the same in the parent component that uses the dropdown component.

    It makes all readable, reusable and with easy maintenance.

    import React, { useState } from 'react';
    
    const Parent = () => {
        const [ selectedMenu, setSelectedMenu ] = useState(undefined)
    
        const menuItems = [
          {text: "item 1"},
          {text: "item 2"},
          {text: "item 3"}
             ]
    
        return (
        <>
            {
            menuItems.map((item, index) => {
                return <Dropdown isActive={selectedMenu === index} onClick={() => setSelectedMenu(index)}/>
            })
          }
        </>
      )
    }
    
    const Dropdown = (props) => {
        const [ selectedItem, setSelectedMenu ] = useState(undefined)
            
        const dropdownContent = [
          {text: "Dropdown item"},
          {text: "Other dropdown item"},
          {text: "Another one"}
        ]
      
        return (
        <div class={`dropdown ${props.isActive ? "is-active" : ""}`}>
            <div class="dropdown-trigger">
                <button class="button"
                        aria-haspopup="true"
                         aria-controls="dropdown-menu"
                         onClick={onClick}>
                    <span>Dropdown button</span>
                    <span class="icon is-small">
                        <i class="fas fa-angle-down" aria-hidden="true"></i>
                    </span>
                </button>
            </div>
            <div class="dropdown-menu" id="dropdown-menu" role="menu">
                <div class="dropdown-content">
                    {
                    dropdownContent.map((item, index) => {
                        return <a href="#" class={`dropdown-item ${selectedItem === index ? "is-active" : ""}`}>{item.text}</a>
                        })
                    }
                </div>
            </div>
        </div>
        )    
    }
    
    export default Dropdown;