如何使用 React Hooks 将特定项目定位为 toggleClick?
How to target a specific item to toggleClick on using React Hooks?
我有一个导航栏组件,其中包含从 CMS 中提取的实际信息。一些导航链接有一个下拉组件 onclick,而其他的则没有。我很难弄清楚如何使用 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;
你的问题是你的状态只处理一个布尔值(是否打开),但你实际上需要多个布尔值(每个菜单项一个 "is open or not")。你可以尝试这样的事情:
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
函数中的新 index 变量,用于识别您正在单击的菜单项。
更新:
我遗漏的一点是初始化,正如@MattYao 在另一个答案中提到的那样。在您的加载数据中,执行以下操作:
data.then(res => {
const navlinks = res.results[0].data.nav;
setLinks(navlinks);
setIsOpen(navlinks.map((link, index) => {index: false}));
});
与您的问题无关,但您可能需要考虑 skipping effects and including a key 您的 .map
我可以看到前两个 useState 挂钩按预期工作。问题是你的第三个 useState() 钩子。
问题很明显,您通过元素列表引用相同的状态变量 isOpen,因此它们都具有相同的状态。要解决这些问题,我建议采用以下方式:
您需要使用数组或 Map 来初始化状态,而不是只有一个 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 函数中,您必须定位 link 对象并将其设置为 true,而不是将所有内容都设置为 true。您可能需要使用 .find() 来定位您正在单击的 link:
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>
如果您只是复制粘贴,上面的代码可能对您不起作用。但它应该让您了解事物应该如何协同工作。
我有一个导航栏组件,其中包含从 CMS 中提取的实际信息。一些导航链接有一个下拉组件 onclick,而其他的则没有。我很难弄清楚如何使用 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;
你的问题是你的状态只处理一个布尔值(是否打开),但你实际上需要多个布尔值(每个菜单项一个 "is open or not")。你可以尝试这样的事情:
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
函数中的新 index 变量,用于识别您正在单击的菜单项。
更新:
我遗漏的一点是初始化,正如@MattYao 在另一个答案中提到的那样。在您的加载数据中,执行以下操作:
data.then(res => {
const navlinks = res.results[0].data.nav;
setLinks(navlinks);
setIsOpen(navlinks.map((link, index) => {index: false}));
});
与您的问题无关,但您可能需要考虑 skipping effects and including a key 您的 .map
我可以看到前两个 useState 挂钩按预期工作。问题是你的第三个 useState() 钩子。
问题很明显,您通过元素列表引用相同的状态变量 isOpen,因此它们都具有相同的状态。要解决这些问题,我建议采用以下方式:
您需要使用数组或 Map 来初始化状态,而不是只有一个 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 函数中,您必须定位 link 对象并将其设置为 true,而不是将所有内容都设置为 true。您可能需要使用 .find() 来定位您正在单击的 link:
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>
如果您只是复制粘贴,上面的代码可能对您不起作用。但它应该让您了解事物应该如何协同工作。