英文:
How can I configure one button to open/close all accordions?
问题
以下是代码部分的翻译:
import React, { useState } from 'react';
const Collapsible = (props) => {
const [open, setOpen] = useState(false);
const toggle = () => {
setOpen(!open);
}
return (
<div>
<button className={props.level} onClick={toggle}>{props.label}</button>
{open && (
<div className="toggle">
{props.children}
</div>
)}
</div>
)
}
export default Collapsible;
如果你有其他需要的翻译,请继续提出。
英文:
I have a component that creates a collapsible accordion, using the following code:
import React, {useState} from 'react';
const Collapsible = (props) =>{
const [open, setOpen] = useState(false);
const toggle = () => {
setOpen(!open);
}
return(
<div>
<button className={props.level} onClick={toggle}>{props.label}</button>
{open && (
<div className="toggle">
{props.children}
</div>
)}
</div>
)
}
export default Collapsible;
I use this in multiple places in my main app, sometimes using accordions within other accordions. In multiple instances, I don't actually know how many accordions will be on the page, because they are dynamically rendered based on the data. With this in mind, I want to create a button in the main app that would open (and another that would close) all accordions, without having a set number in mind, and without rendering them all in the main app (i.e. some accordions are rendered in other components, that are then imported into the main app).
I've tried using ref hooks to accomplish this:
- Added ref in the Collapsible component's button, accessed from parent through props:
<button className={props.level} onClick={toggle} ref={props.innerRef}>{props.label}</button>
- Adding the following ref in the main app:
const childRef = useRef();
const openClick = () => {
childRef.state = true;
}
const closeClick = () => {
childRef.state = false;
}
- Using the following buttons in the main app:
<button onClick = {openClick}>
Expand all
</button>
<button onClick = {closeClick}>
Collapse all
</button>
- Adding the ref to the accordion when rendering:
<Collapsible label="" level="content" innerRef={childRef}>
This does absolutely nothing, likely because you can't access state in the way I'm attempting in #2, but I thought it was worth a shot...
Any thoughts on whether this is doable?
答案1
得分: 1
你可以使用 Redux
。
- 当渲染你的手风琴时,为它们赋予一个特定的 ID 并将其保存在存储中。
- 创建一个切片
openAllAccordions
,遍历 ID 并将属于该 ID 的手风琴设置为打开(open=true)。 - 创建一个切片
closeAllAccordions
,遍历 ID 并将属于该 ID 的手风琴设置为关闭(open=false)。
英文:
You can use Redux
.
- When you render you accordions give them a specific id and save it in the store.
- Create a slice
openAllAccordions
that loop throw IDs and set the accordion belongs to that id to open=true - Create a slice
closeAllAccordions
that loop throw IDs and set the accordion belongs to that id to open=false
答案2
得分: 1
很常见有一个或多个需要协调的组件实例的任意集合。我成功使用的一种方法是创建一个 Context,并使用相关联的 hook 让组件可以注册。该 hook 返回共享状态的视图以及修改该状态的函数,以满足你的需求。
在这里,你可以创建一个存储每个注册组件的 opener
函数的 Context,并提供 openAll
/closeAll
函数:
const AccordionProvider = ({ children }) => {
const [openers] = useState(new Set());
// 新创建的可折叠项是否应该自动展开?
// (为了支持递归展开是必要的)
const [defaultOpen, setDefaultOpen] = useState(false);
const register = useCallback(
(opener) => {
openers.add(opener);
return () => openers.delete(opener); // 为 `useEffect` 返回一个取消注册的函数
},
[openers]
);
const openAll = useCallback(() => {
setDefaultOpen(true);
openers.forEach((opener) => opener(true));
}, [setDefaultOpen, openers]);
const closeAll = useCallback(() => {
setDefaultOpen(false);
openers.forEach((opener) => opener(false));
}, [setDefaultOpen, openers]);
return (
<AccordionContext.Provider
value={{ register, openAll, closeAll, defaultOpen }}
children={children}
/>
);
};
... 还有一个被每个子组件调用的 hook,通过该 hook 在上下文中注册,并返回熟悉的 toggle
/open
值:
const useAccordionAsClient = () => {
const { register, defaultOpen } = useContext(AccordionContext);
const [open, opener] = useState(defaultOpen);
const toggle = useCallback(() => opener((open) => !open), [opener]);
useEffect(() => register(opener), [register, opener]);
return { toggle, open };
};
同样,有一个用于执行大规模操作的独立 hook 也很方便:
const useAccordionAsManager = () => {
const { openAll, closeAll } = useContext(AccordionContext);
return { openAll, closeAll };
};
请注意,为简单起见,这里只是使用了单独的 opener
(也称为 setOpen
)函数作为每个注册组件的唯一标识符。一个灵活的替代方案是使用其他标识符,这样你可以在导航等情况下打开/关闭任意的手风琴。
英文:
It's pretty common to have a more-or-less arbitrary collection of component instances that need some coordination. An approach that I have had success with is to create a Context with an associated hook that components can use to register. The hook returns a view of the shared state and functions to modify that state, subject to your needs.
Here, you could create a Context that stores the opener
functions for each of the registered components, and provides openAll
/closeAll
functions:
const AccordionProvider = ({ children }) => {
const [openers] = useState(new Set());
// Should new collapsibles spring open when created?
// (necessary to support recursive opening)
const [defaultOpen, setDefaultOpen] = useState(false);
const register = useCallback(
(opener) => {
openers.add(opener);
return () => openers.delete(opener); // return a deregister function for `useEffect`
},
[openers]
);
const openAll = useCallback(() => {
setDefaultOpen(true);
openers.forEach(opener => opener(true)),
}, [setDefaultOpen, openers]);
const closeAll = useCallback(() => {
setDefaultOpen(false);
openers.forEach(opener => opener(false)),
}, [setDefaultOpen, openers]);
return (
<AccordionContext.Provider
value={{ register, openAll, closeAll, defaultOpen }}
children={children}
/>
);
};
... and a hook called by each child that registers with the context, and returns your familiar toggle
/open
values:
const useAccordionAsClient = () => {
const { register, defaultOpen } = useContext(AccordionContext);
const [open, opener] = useState(defaultOpen);
const toggle = useCallback(() => opener((open) => !open), [opener]);
useEffect(() => register(opener), [register, opener]);
return { toggle, open };
};
It's also handy to have a separate hook for the actions that you can execute en masse:
const useAccordionAsManager = () => {
const { openAll, closeAll } = useContext(AccordionContext);
return { openAll, closeAll };
};
Note that for simplicity this is just using the individual opener
(aka setOpen
) functions as unique identifiers for each registered component. A flexible alternative would be use some other identifier, so you could open/close arbitrary accordions on navigation etc.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论