Как настроить таргетинг на определенный элемент для переключения кликов с помощью React Hooks?

У меня есть компонент навигационной панели, в котором эта фактическая информация извлекается из CMS. Некоторые навигационные ссылки имеют раскрывающийся список при нажатии, а другие — нет. Мне трудно понять, как настроить таргетинг на определенный индекс меню с помощью React Hooks - в настоящее время onClick открывает ВСЕ раскрывающиеся меню одновременно, а не конкретное, на которое я нажал.

Свойство toggleOpen передается стилизованному компоненту на основе обработчика события handleDropDownClick.

Вот мой компонент.

const NavBar = props => {
 const [links, setLinks] = useState(null);
 const [notFound, setNotFound] = useState(false);
 const [isOpen, setIsOpen] = useState(false);

 const fetchLinks = () => {
   if (props.prismicCtx) {
     // We are using the function to get a document by its uid
     const data = props.prismicCtx.api.query([
       Prismic.Predicates.at('document.tags', [`${config.source}`]),
       Prismic.Predicates.at('document.type', 'navbar'),
     ]);
     data.then(res => {
       const navlinks = res.results[0].data.nav;
       setLinks(navlinks);
     });
   }
   return null;
 };

 const checkForLinks = () => {
   if (props.prismicCtx) {
     fetchLinks(props);
   } else {
     setNotFound(true);
   }
 };

 useEffect(() => {
   checkForLinks();
 });

 const handleDropdownClick = e => {
   e.preventDefault();
   setIsOpen(!isOpen);
 };

 if (links) {
   const linkname = links.map(item => {
     // Check to see if NavItem contains Dropdown Children
     return item.items.length > 1 ? (
       <Fragment>
         <StyledNavBar.NavLink onClick = {handleDropdownClick} href = {item.primary.link.url}>
           {item.primary.label[0].text}
         </StyledNavBar.NavLink>
         <Dropdown toggleOpen = {isOpen}>
           {item.items.map(subitem => {
             return (
               <StyledNavBar.NavLink href = {subitem.sub_nav_link.url}>
                 <span>{subitem.sub_nav_link_label[0].text}</span>
               </StyledNavBar.NavLink>
             );
           })}
         </Dropdown>
       </Fragment>
     ) : (
       <StyledNavBar.NavLink href = {item.primary.link.url}>
         {item.primary.label[0].text}
       </StyledNavBar.NavLink>
     );
   });
   // Render
   return (
     <StyledNavBar>
       <StyledNavBar.NavContainer wide>
         <StyledNavBar.NavWrapper row center>
           <Logo />
           {linkname}
         </StyledNavBar.NavWrapper>
       </StyledNavBar.NavContainer>
     </StyledNavBar>
   );
 }
 if (notFound) {
   return <NotFound />;
 }
 return <h2>Loading Nav</h2>;
};

export default NavBar;
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
1
0
2 778
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Ваша проблема в том, что ваше состояние обрабатывает только логическое значение (открыто или нет), но на самом деле вам нужно несколько логических значений (по одному «открыто или нет» для каждого пункта меню). Вы можете попробовать что-то вроде этого:

const [isOpen, setIsOpen] = useState({});

const handleDropdownClick = e => {
    e.preventDefault();
    const currentID = e.currentTarget.id;
    const newIsOpenState = isOpen[id] = !isOpen[id];
    setIsOpen(newIsOpenState);
};

И, наконец, в вашем HTML:

const linkname = links.map((item, index) => {
    // Check to see if NavItem contains Dropdown Children
    return item.items.length > 1 ? (
        <Fragment>
            <StyledNavBar.NavLink id = {index} onClick = {handleDropdownClick} href = {item.primary.link.url}>
               {item.primary.label[0].text}
            </StyledNavBar.NavLink>
            <Dropdown toggleOpen = {isOpen[index]}>

    // ... rest of your component

Обратите внимание на новую переменную показатель в функции .map, которая используется для определения того, какой пункт меню вы нажимаете.

Обновлено:

Один момент, который мне не хватало, - это инициализация, как упоминалось в другом ответе @MattYao. Внутри ваших данных загрузки сделайте следующее:

data.then(res => {
    const navlinks = res.results[0].data.nav;
    setLinks(navlinks);
    setIsOpen(navlinks.map((link, index) => {index: false}));
});

Не имеет отношения к вашему вопросу, но вы можете рассмотреть пропуск эффектов и включить ключ в свой .map

Здорово! Это, кажется, на правильном пути. Однако у меня есть новая проблема, но, вероятно, что-то очевидное, что я упускаю - моя поддержка toggleOpen теперь нет передается компоненту Dropdown. Любые подсказки здесь? Ниже все, что я изменил: const [isOpen, setIsOpen] = useState([]); const handleDropdownClick = index => { const clicked = isOpen; clicked[index] = !clicked[index]; setIsOpen(clicked); }; <Dropdown toggleOpen = {isOpen[i]}> (i относится к другому индексу из карты списка)

geeberry 17.04.2019 05:36

Что вы подразумеваете под «я ссылаюсь на другой индекс»? Индекс должен быть таким же, как у функции .map. Одна из идей — попытаться напечатать isOpen[i], чтобы увидеть, что там происходит.

Bruno Monteiro 17.04.2019 22:50

Я только что заметил, что мне не хватает инициализации, которую ответ ниже охватывает в пункте 2. Я обновил свой ответ, чтобы включить это :)

Bruno Monteiro 17.04.2019 22:57

Я вижу, что первые два хука useState работают, как и ожидалось. Проблема в вашем третьем хуке useState().

Проблема довольно очевидна: вы ссылаетесь на одну и ту же переменную состояния isOpen по списку элементов, поэтому все они имеют одинаковое состояние. Для устранения проблем предлагаю следующий способ:

  1. Вместо того, чтобы иметь одно значение isOpen, вам нужно будет инициализировать состояние с помощью массива или карты, чтобы вы могли ссылаться на каждое отдельное:

    const initialOpenState = [] // or using ES6 Map - new Map([]);
    
  2. В обратном вызове функции fetchLink инициализируйте значения массива состояний isOpen равными false. Итак, вы можете разместить его здесь:

    data.then(res => {
       const navlinks = res.results[0].data.nav;
       setLinks(navlinks);
    
       // init your isOpen state here
       navlinks.forEach(link => isOpen.push({ linkId: link.id, value: false })) //I suppose you can get an id or similar identifers
     });
    
  3. В вашей функции handleClick вы должны настроить таргетинг на объект ссылки и установить для него значение true, а не устанавливать для всего значения true. Возможно, вам придется использовать .find(), чтобы найти ссылку, по которой вы щелкаете:

    handleClick = e => {
       const currentOpenState = state;
       const clickedLink = e.target.value // use your own identifier
       currentOpenState[clickedLink].value = !currentOpenState[clickedLink].value;
       setIsOpen(currentOpenState);
    }
    
  4. Обновите свой компонент, чтобы использовалось правильное состояние isOpen:

    <Dropdown toggleOpen = {isOpen[item].value}> // replace this value
       {item.items.map(subitem => {
         return (
           <StyledNavBar.NavLink href = {subitem.sub_nav_link.url}>
             <span>{subitem.sub_nav_link_label[0].text}</span>
           </StyledNavBar.NavLink>
         );
       })}
     </Dropdown>
    

Приведенный выше код может не работать для вас, если вы просто скопируете и вставите. Но это должно дать вам представление о том, как все должно работать вместе.

Другие вопросы по теме