본문 바로가기

Frontend

"jsx-a11y/no-noninteractive-element-interactions" 에러 해결

개요

  • React + TypeScript 프로젝트를 진행하던 중 li 태그에 onClick 속성을 추가하니 Eslint 에러가 발생했다.
  • 위 에러가 발생하는 이유와 해결 방법에 대해 알아보자.

코드

type RestaurantRowProps = {
  restaurant:Restaurant
}

function RestaurantRow({ restaurant }:RestaurantRowProps) {
  const [, cartStore] = useCartStore();
  const handleClick = (food:Food) => () => {
    cartStore.addCart(food);
  };
  return (
    <tr>
      <td>{restaurant.name}</td>
      <td>
        <ul>
          {restaurant.menu.map((food) => (
            <li key={food.id} onClick={handleClick(food)}> // ❗️Error
                <img src={food.image} alt={food.image} />
                <p>{food.name}</p>
                <p>{`${convertKRW(food.price)}원`}</p>
            </li>
          ))}
        </ul>
      </td>
    </tr>
  );
}

export default RestaurantRow;

원인

  • 비대화형 HTML 요소와 비대화형 ARIA의 역할은 UI의 컨텐츠 및 컨테이너를 나타내는 것이다.
  • 그러므로 이들은 Mouse, Keyboard 이벤트 핸들러를 지원하지 않는다.
  • li 요소는 비대화형 HTML 요소이다.

해결방법

1. Mouse, Keyboard 이벤트는 가급적 대화형 요소에만 사용한다.

대표적인 대화형 요소로는 button, link 등이 있다.

type RestaurantRowProps = {
  restaurant:Restaurant
}

function RestaurantRow({ restaurant }:RestaurantRowProps) {
  const [, cartStore] = useCartStore();
  const handleClick = (food:Food) => () => {
    cartStore.addCart(food);
  };
  return (
    <tr>
      <td>{restaurant.name}</td>
      <td>
        <ul>
          {restaurant.menu.map((food) => (
            <li key={food.id}>
              <button type="button" onClick={handleClick(food)}> // ✅ button에 onClick 추가
                <img src={food.image} alt={food.image} />
                <p>{food.name}</p>
                <p>{`${convertKRW(food.price)}원`}</p>
              </button>
            </li>
          ))}
        </ul>
      </td>
    </tr>
  );
}

export default RestaurantRow;

2. role = "presentation" 속성 추가

presentation 속성을 추가함으로써 요소의 컨텐츠 또는 컨테이너 의미 값이 그대로 유지된다.

또한, 해당 요소를 무시해야 한다는 것을 보조 기술에게 알려준다.

type RestaurantRowProps = {
  restaurant:Restaurant
}

function RestaurantRow({ restaurant }:RestaurantRowProps) {
  const [, cartStore] = useCartStore();
  const handleClick = (food:Food) => () => {
    cartStore.addCart(food);
  };
  return (
    <tr>
      <td>{restaurant.name}</td>
      <td>
        <ul>
          {restaurant.menu.map((food) => (
            <li role="presentation" key={food.id} onClick={handleClick(food)}> //✅ li요소에 role="presentation" 추가
              <img src={food.image} alt={food.image} />
              <p>{food.name}</p>
              <p>{`${convertKRW(food.price)}원`}</p>
            </li>
          ))}
        </ul>
      </td>
    </tr>
  );
}

export default RestaurantRow;

참조

eslint - jsx-a11y/no-noninteractive-element-interactions