Search code examples
reactjsparent-childreact-hookssetstate

How can I unset an active class on each child component while setting it to active for the clicked child


I am trying to set an active class on child component B while disabling the active class on child component A when I click on B.

So far I have tried using hooks in the parent class where I unset the active prop on all childs by using setActive(''); followed by setting the class of the current target to link--active by using e.currentTarget.className === 'link--active' ? e.currentTarget.className = '' : e.currentTarget.className = 'link--active';. Sadly all it does at this moment is add the class or remove the class on the clicked child.

Parent:

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

  const navigate = (e) => {
    setActive('');
    e.currentTarget.className === 'link--active' ? e.currentTarget.className = '' : e.currentTarget.className = 'link--active';
  };

and in the return statement:

{menuItems.map((item, index) => (
  <li key={index} >
    <NavLink target={item} onClick={(e) => navigate(e)} active={active} />
  </li>
))}

Children:

<a href="#"
   onClick={props.onClick} 
   className={props.active}>
   {props.target}
</a>

Edit:

After using the solution from Ori Drori, the active class was set on the clicked NavLink and removed from the rest. Since I wanted the onClick to be the navigate function, all I changed was set the onClick in the parent to navigate and have the navigate function call setActive by using id as param and calling setActive in the navigate function with id as a param again. The classes now look like this:

Parent:

const [active, setActive] = useState(null);

const navigate = (id) => {
  setActive(id);
};

return (
    {menuItems.map((item) => (
      <li key={item.id} >
        <NavLink 
          {...item}
          isActive={active === item.id}
          onClick={navigate} />
      </li>
    ))}
)

Child:

const NavLink = ({id, target, isActive, onClick}) => {
  return (
      <a href="#"
        onClick={useCallback(() => onClick(id), [id, onClick])} 
        className={isActive ? 'active' : ''}>
        {target}
      </a>
  );
}

Solution

  • Pass setActive to the NavLinks. When a NavLink is clicked it sets it's id via setActive. Each item also receives the isActive property, which is true if the active state matches it's id.

    const { useCallback, useState } = React
    
    const NavLink = ({ id, target, isActive, onClick }) => (
      <a href="#"
         onClick={useCallback(() => onClick(id), [id])} 
         className={`navLink ${isActive ? 'active' : ''}` }>
         {target}
      </a>
    )
    
    const Parent = ({ menuItems }) => {
      const [active, setActive] = useState(null);
    
      return (
        <ul>
          {menuItems.map((item) => (
            <li key={item.id} >
              <NavLink 
                {...item} 
                onClick={setActive} 
                isActive={active === item.id} />
            </li>
          ))}
        </ul>
      )
    }
    
    const items = [{ id: 0, target: 'Ready' }, { id: 1, target: 'Player' }, { id: 2, target: 'One' }]
    
    ReactDOM.render(
      <Parent menuItems={items} />,
      demo
    )
    .navLink {
      color: blue;
      text-decoration: none;
    }
    
    .active {
      color: red;
    }
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    
    <div id="demo"></div>