Dynamically populating nested react-native-collapsible

892 Views Asked by At

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 Accordions and AccordionItems 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.

2

There are 2 best solutions below

0
On BEST ANSWER

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.

0
On

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