Search code examples
javascriptreactjsjsxelementvirtual-dom

Does e.target.classList.{add || remove || toggle } dangerouslySetInnerHtml?


Wanted to ask people with more experience with React about this, since I can't seem to find a definitive answer. I know you don't want to add text to the DOM directly, which is why React makes you write dangerouslySetInnerHtml in order to set it.

But what about classList.{ add || remove || toggle }? Is using classList.add() || .remove() tantamount to dangerouslySetInnerHtml? Should I be worried about doing this, too?

Case in point - a function that renders a list from inside a component styled with bootstrap css.

Problem: Set add/remove .active to li className on mouse hover.

import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';

const RenderList = (array, position = 'text-center') => {
    const handleMouseEnter = e => {
        e.target.classList.add('active');
    };

    const handleMouseLeave = e => {
        e.target.classList.remove('active');
    };

    return (
        <ul className={`list-group ${position}`}>
            {array.map(({ href, key, label, rel, target }) => (
        <a key={key} href={href} rel={rel} target={target} fontSize={1.2}>
          <li
            className={`list-group-item '${key}'`}
          
            data-id={key}
            onMouseEnter={handleMouseEnter}
            onMouseLeave={handleMouseLeave}
          >
            {label}
          </li>
        </a>
            ))}
        </ul>
    );
};
export default RenderList;

The problem is - I've tried setting this in state, but it affects all lis at once (I've seen this issue in other questions - albeit, not for functional components, but for classes)

Is this safe? I don't see any warnings in my browser.

If it's not safe, what's the alternative? I tried using a data-id attribute from the key for the li, but I wasn't quite able to wrap my head around useState for a single element...

Edit: A question was suggested as a possible answer, but it recommends making a separate instance of state for each list item, which doesn't seem like a very good solution.


Solution

  • Mutating the DOM manually is an anti-pattern in React

    React is a data-first UI lib, meaning that the data dictates how it should render, and it (almost) never need to read the DOM. It only writes to the DOM, and with its internal diffing algorithm, it computes the minimal chunks to update in the DOM.

    When mutating the DOM ourselves, React won't be aware of our changes and might wipe our efforts in the next render phase.

    Toggling styling on mouse hover

    Whenever possible, favor CSS selectors over JavaScript to style your components. In this case, it would be a simple selector:

    .list-group li:hover {
      /* `.active` styles here */
    }
    

    How to toggle classes on each item?

    To avoid managing the state of each individual items in the list component, I'd make an Item component which manages its own state and events, leaving the parent list to focus on its actual logic.

    const { useState } = React;
    
    // Manages its own state and events, making it 
    // possible to toggle the class here instead.
    const Item = ({ to }) => {
      const [active, setActive] = useState(false);
      
      return (
        <li
          className={active? 'active' : ''} 
          onMouseEnter={() => setActive(true)}
          onMouseLeave={() => setActive(false)}
        >
          <a data-link={to}>{to}</a>
        </li>
      );
    };
    
    // No more class toggling logic needed here.
    const List = ({ items }) => (
      <ul>
        {items.map((itemProps) => <Item {...itemProps} />)}
      </ul>
    );
    
    ReactDOM.render(
      <List items={[{ to: '/home' }, { to: '/foo' }]} />,
      document.querySelector('[data-role-react-root]')
    );
    .active {
      background: red;
    }
    <div data-role-react-root></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>