Как правильно отслеживать, какой элемент активен в дереве, в react

#javascript #reactjs #carbon-design-system

Вопрос:

Я использую компонент SideNav Carbon в качестве элемента управления навигацией по дереву для страницы. Листья могут быть заданы active либо isActive логическим свойством, либо путем добавления aria-current='page' . Если лист находится в подменю, меню расширяется, если активен какой-либо из его дочерних элементов. Отслеживание активных товаров не выходит из коробки, его нужно добавить. У меня есть два вопроса:

  1. Каков был бы правильный способ реализации активного отслеживания товаров? Я могу придумать два способа: добавить onClick() к каждому SideNavMenuItem или, как я это сейчас реализовал: собрать массив ссылок, затем useEffect() удалить активный класс из всех и добавить его в выбранный
  2. Как правильно пометить активный элемент при монтировании содержащего его компонента (например, при обновлении страницы или переходе на страницу с помощью закладки). Мой ссылочный код устанавливает активное состояние, добавляя требуемый класс в пункт меню, но SideNavMenu он откроется только в том случае, если активен какой — либо из его дочерних элементов, что является свойством, которое я не могу установить с помощью ссылки.

Мой текущий пример основан на небольшом дереве из 8 элементов, но мне придется расширить его до гораздо большего масштаба.

 const MySidebar = () => {

  const menuRefs = useRef(new Array());

  useEffect(() => {
    const className = "bx--side-nav__link--current";
    menuRefs.current.forEach(ref => {ref.classList.remove(className)})
    const ref = menuRefs.current.filter((ref) => ref.baseURI === ref.href);
    if (ref) {
      ref[0].classList.add(className);
    }
  });

  return (
      <SideNav
      className="my-nav"
      isFixedNav
      expanded={true}
      isChildOfHeader={false}
      aria-label="Side navigation"
      >
        <SideNavItems>
          <SideNavMenuItem ref={(element) => menuRefs.current[0] = element} element={Link} to="page1">Page 1</SideNavMenuItem>
          <SideNavMenuItem ref={(element) => menuRefs.current[1] = element} element={Link} to="page2">Page 2</SideNavMenuItem>
          <SideNavMenu  title='Some submenu'>
            <SideNavMenuItem ref={(element) => menuRefs.current[2] = element} element={Link} to="page3">Page 3</SideNavMenuItem>
            <SideNavMenuItem ref={(element) => menuRefs.current[3] = element} element={Link} to="page4">Page 4</SideNavMenuItem>
            <SideNavMenuItem ref={(element) => menuRefs.current[4] = element} element={Link} to="page5">Page 5</SideNavMenuItem>
            <SideNavMenuItem ref={(element) => menuRefs.current[5] = element} element={Link} to="page6">Page 6</SideNavMenuItem>
            <SideNavMenuItem ref={(element) => menuRefs.current[6] = element} element={Link} to="page7">Page 7</SideNavMenuItem>
          </SideNavMenu>
          <SideNavMenuItem ref={(element) => menuRefs.current[7] = element} element={Link} to="page8">Page 8</SideNavMenuItem>
        </SideNavItems>
      </SideNav>
  );
};
 

Ответ №1:

Наличие активного элемента в памяти-это не лучший способ, afais, вы должны сделать вывод об этом по URL-адресу. Вы можете использовать hashed routes или route parameters для отслеживания активного элемента, нажать его на URL-адрес после нажатия, использовать его непосредственно из URL-адреса в компоненте. Таким образом, он не потеряет активный элемент при обновлении, а URL-адреса также можно добавлять в закладки и делиться ими.

Комментарии:

1. Активный элемент не сохраняется в памяти. URL-адрес указывает, какой из них является активным, поскольку каждый элемент загружает определенную страницу. Что мне трудно понять, так это то, как я могу связать URL-адрес со свойством элемента isActive

2. @RekaB ты используешь ReactRouter ? так, например , если вы нажмете активный элемент для поиска параметров, это будет похоже на то, как http://localhost:300/home?page=page6 вы могли бы получить значение window.location.search , если оно находится в пути, вам придется выполнить некоторую обработку строк. Если вы используете react-маршрутизатор, который рекомендуется, проще было бы match.params.{paramId} получить доступ к параметрам пути. для поиска же.

3. проблема не в том, чтобы определить, активна ли страница, а в том, чтобы элемент SideNav осознал это. Теперь я решил проблему, используя NavLink вместо Link этого, но мне все еще нужно иметь возможность открыть SideNavMenu. Меня также интересовала общая передовая практика настройки свойств потенциально динамически генерируемых детей

4. @RekaB, если вы можете предоставить codesandbox.io минимальный пример, я могу помочь.

5. разве это не отслеживание активной страницы? в чем проблема ?

Ответ №2:

Для открытия элементов дерева/отслеживания, когда пользователи переходят по ссылкам внутри раздела содержимого, я закончил тем, что обернул оригинал SideNavMenu своим собственным компонентом, который рекурсивно проверяет, есть ли у кого-либо из детей ссылка на текущее местоположение. Я разделил проверку на три метода, чтобы if ни в одном из них не было поля утверждений. Я все еще не знаю, правильный ли это подход, но он работает.

 class MySideNavMenu extends React.Component {
  hasActiveChild = (child) => {
    if (child.props.element) { // leaf
      return this.props.location.pathname === child.props.to;
    }
    if (child.props.children) { // node
      return this.checkChildren(child.props.children);
    }
  };

  checkChildren = (children) => {
    return Array.isArray(children)
      ? children.some((child) => {
          return this.hasActiveChild(child);
        })
      : this.hasActiveChild(children);
  };

  isExpanded = () => {
    const children = this.props.children;
    if (children) {
      // if we have children, either a single or multiple, find if it is active
      return this.checkChildren(children);
    }

    return false;
  };

  render() {
    return (
      <SideNavMenu defaultExpanded={this.isExpanded()} title={this.props.title}>
        {this.props.children}
      </SideNavMenu>
    );
  }
}

export default withRouter(MySideNavMenu);