У меня есть компонент навигационной панели, в котором эта фактическая информация извлекается из 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;





Ваша проблема в том, что ваше состояние обрабатывает только логическое значение (открыто или нет), но на самом деле вам нужно несколько логических значений (по одному «открыто или нет» для каждого пункта меню). Вы можете попробовать что-то вроде этого:
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
Что вы подразумеваете под «я ссылаюсь на другой индекс»? Индекс должен быть таким же, как у функции .map. Одна из идей — попытаться напечатать isOpen[i], чтобы увидеть, что там происходит.
Я только что заметил, что мне не хватает инициализации, которую ответ ниже охватывает в пункте 2. Я обновил свой ответ, чтобы включить это :)
Я вижу, что первые два хука useState работают, как и ожидалось. Проблема в вашем третьем хуке useState().
Проблема довольно очевидна: вы ссылаетесь на одну и ту же переменную состояния isOpen по списку элементов, поэтому все они имеют одинаковое состояние. Для устранения проблем предлагаю следующий способ:
Вместо того, чтобы иметь одно значение isOpen, вам нужно будет инициализировать состояние с помощью массива или карты, чтобы вы могли ссылаться на каждое отдельное:
const initialOpenState = [] // or using ES6 Map - new Map([]);
В обратном вызове функции 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
});
В вашей функции 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);
}
Обновите свой компонент, чтобы использовалось правильное состояние 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>
Приведенный выше код может не работать для вас, если вы просто скопируете и вставите. Но это должно дать вам представление о том, как все должно работать вместе.
Здорово! Это, кажется, на правильном пути. Однако у меня есть новая проблема, но, вероятно, что-то очевидное, что я упускаю - моя поддержка toggleOpen теперь нет передается компоненту Dropdown. Любые подсказки здесь? Ниже все, что я изменил:
const [isOpen, setIsOpen] = useState([]); const handleDropdownClick = index => { const clicked = isOpen; clicked[index] = !clicked[index]; setIsOpen(clicked); }; <Dropdown toggleOpen = {isOpen[i]}>(i относится к другому индексу из карты списка)