Reputation: 1
I need to populate a menu with items from an api request.
I made some sample items; const items
. Some of the menu items have children, and the children can also have children, so I need to nest several levels of menu items.
I made an Accordion()
with Collapsible from react-native-collapsible and an AccordionItem()
for items that have no children.
function App()
renders the menu items twice. Once by manually adding Accordion
s and AccordionItem
s and once by mapping items[]
. RootMenuItem()
is called for each top level item and renders that item and its sub-items by recursion.
When manually adding each item it works the way I want it to. I need to populate the menu programatically, but nested accordions rendered by RootMenuItem()
are misbehaving on android and iOS. When testing in Web on snack.io it seems to be working fine.
Here is a snack with my complete App.js: https://snack.expo.dev/@dissar/nested-collapsibles
Am I doing something wrong? Does anybody have any tips for doing this in a better way?
PS: The dynamically rendered items have weird widths when testing on snack.io, but don't worry about that.
Upvotes: 0
Views: 1206
Reputation: 661
I have a better solution without using any 3rd party library. This is completely customised and easy to understand. I used the same format of data as you used.
first of all, we have a component
const [active, setActive] = useState(null);
return (
<ScrollView
style={{ marginTop: 50 }}
contentContainerStyle={styles.container}>
{arr.map((x, i) => (
<Item
key={x.name}
active={active}
i={i}
setActive={setActive}
child={x.child}
/>
))}
</ScrollView>
);
then for the list items and their child
function Item({ i, active, setActive, child }) {
const onPress = () => {
LayoutAnimation.easeInEaseOut();
setActive(i == active ? null : I);
};
const [childActive, setChildActive] = useState(null);
const open = active == I;
return (
<TouchableOpacity style={styles.item} onPress={onPress} activeOpacity={1}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Text>Header - {i + 1}</Text>
{child.length ? <Text>{open ? 'close' : 'open'}</Text> : null}
</View>
{open &&
child.map((x, i) => {
if (x.child.length) {
return (
<Item
key={x}
active={childActive}
i={i}
setActive={setChildActive}
child={x.child}
/>
);
}
return (
<Text key={x} style={styles.subItem}>
- SOME DATA
</Text>
);
})}
</TouchableOpacity>
);
}
It's a completely dynamic process, you can extend the chain as long as you want. You can also check out the expo to look at its works. https://snack.expo.dev/@akash0208/forlorn-popsicle
Upvotes: 0
Reputation: 1
I seem to have fixed it myself by removing the View on line 46 and 56;
function RootMenuItem({item}){
if(item.children.length > 0) {
return(
<View style={{flex: 1}} key={item.id}> // <---- I removed this
<Accordion item={item} style={styles.menuItemView}>
{
item.children.map(child => (
<View style={{paddingLeft: 18}} key={child.id}>
<RootMenuItem item={child} style={{paddingLeft: 10}}/>
</View>
))
}
</Accordion>
</View> // <---- Also removed this
)
}
else return (
<AccordionItem item={item}/>
)
}
Not really sure though why that View made the nested accordions not work as they should. Please let me know if you have the answer.
Upvotes: 0