Search code examples
reactjsecmascript-6react-state-management

REACT.JS: How to loop over all NavBar buttons and remove their class and add "active" class to the clicked button


I am trying to make a simple NavBar with React.js. The problem I found myself in is the looping over all nav buttons and remove the "active" className and then add "active" to just that one clicked button.

I managed to make a state that toggles "active" to true on the clicked element which then in the className attribute does this If statement:

className={this.state.active ? "nav-item nav-link active" : "nav-item nav-link"}

Here is the full code:

import React, { Component } from 'react';

class NavButton extends Component {
    state = {
        active: false
    }

    setActive = () => {
        this.setState({
            active: !this.state.active
        })
    }

    render() {
        return (
            <a 
            className={this.state.active ? "nav-item nav-link active" : "nav-item nav-link"} 
            href={this.props.href}
            onClick={this.setActive}> {this.props.title} 
            </a>
        )
    }
}

class NavBar extends Component {

    buttons = [
        {
            title: "Home",
            key: 0
        },
        {
            title: "Team",
            key: 1
        },
        {
            title: "Discord",
            key: 2
        },
        {
            title: "Gallery",
            key: 3
        },
        {
            title: "Download",
            key: 4
        }
    ]

    render() {
        return (
            <nav className="navbar" id="navbarMain">
                <div></div>
                <div className="navbar-nav flex-row">
                    {this.buttons.map(button => <NavButton title={button.title} key={button.key} />)}
                </div>
                <div></div>
            </nav>
        )
    }
}

export default NavBar

This works, for just one element (don't mind that the active state goes false when it's true. The problem is, how would I do it in the React way to remove the active className in all other buttons?

With plain JS i have no issues to do that, i just loop over all elements that have the className "navbar-item" and set their classnames to be without the "active" one then add " active" to the pressed element like in this example https://www.w3schools.com/howto/howto_js_tabs.asp

Would you guys be able to help and tell me what would be the best react way to do this?

Much appreciated!


Solution

  • A common pattern for these use-cases is to keep the relevant state in the parent, so that it is the parent (NavBar) that keeps track of which child (NavButton) is "active". The NavButton can then become a stateless component which takes "active" as a prop.

    const NavButton = ({active, title, href, onSetActive}) => {
            return (
                <button 
                className={active ? "nav-item nav-link active" : "nav-item nav-link"} 
                href={href}
                onClick={onSetActive} > 
                  {title} 
                </button>
            )
    }
    
    class NavBar extends React.Component {
    		constructor(props) {
        	super(props);
        	this.state = {
          	activeIndex: 0, // keep the active index in state
          	buttons: [
            {
                title: "Home",
                key: 0
            },
            {
                title: "Team",
                key: 1
            },
            {
                title: "Discord",
                key: 2
            },
            {
                title: "Gallery",
                key: 3
            },
            {
                title: "Download",
                key: 4
            }
        ]
          }
        }
        
        handleChangeActive(newActiveIndex) {
        	this.setState({activeIndex: newActiveIndex});
        }
    
        render() {
        		const {activeIndex} = this.state;
            return (
                <nav className="navbar" id="navbarMain">
                    <div></div>
                    <div className="navbar-nav flex-row">
                        {this.state.buttons.map((button, buttonIndex) => 
                        	/* determine which nav button is active depending on the activeIndex state */
                        	<NavButton onSetActive={ () => this.handleChangeActive(buttonIndex)} active={buttonIndex === activeIndex } title={button.title} key={button.key} />)}
                    </div>
                    <div></div>
                </nav>
            )
        }
    }
    
    ReactDOM.render(<NavBar />, document.querySelector("#app"));
    body {
      background: #20262E;
      padding: 20px;
      font-family: Helvetica;
    }
    
    #app {
      background: #fff;
      border-radius: 4px;
      padding: 20px;
      transition: all 0.2s;
    }
    
    li {
      margin: 8px 0;
    }
    
    h2 {
      font-weight: bold;
      margin-bottom: 15px;
    }
    
    .done {
      color: rgba(0, 0, 0, 0.3);
      text-decoration: line-through;
    }
    
    input {
      margin-right: 5px;
    }
    .nav-item.nav-link {
      background: grey;
    }
    .nav-item.nav-link.active {
      background: red;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
    <body>
    
    <div id="app"></div>
    </body>