I'm building a React Native app with a custom dropdown component where one element can be expanded at a time. The component uses a FlatList
to display a list of items, and each item can be expanded to show more details when pressed. I'm managing the expanded state using useState, but when I press on one of the Pressable elements, all items except the one I selected are expanded.
Here's the code for my CustomDropdown component:
import { createContext, useContext, useState } from "react";
import { Text, View, StyleSheet, FlatList, Pressable } from "react-native";
import FontAwesome from '@expo/vector-icons/FontAwesome';
export default function CustomDropdown(props: any) {
const { channels, friends, groups, services, setChannels, setFriends, setGroups, setServices } = props
const [ isExpanded , setIsExpanded] = useState(null)
const toggleState = (key) => {
switch (key.toLowerCase()) {
case 'channels':
setChannels((prev: any)=>!prev)
setIsExpanded(channels)
break;
case 'friends':
setFriends((prev: any)=>!prev)
setIsExpanded(friends)
break
case 'groups':
setGroups((prev: any)=>!prev)
setIsExpanded(groups)
break
case 'services':
setServices((prev: any)=>!prev)
setIsExpanded(services)
break
default:
break;
}
}
return(
<View style={styles.dropDown}>
<FlatList
data={[
{key: 'Channels', state: channels, value: 'channel'},
{key: 'Friends', state: friends, value: 'friend'},
{key: 'Groups', state: groups, value: 'group',},
{key: 'Services', state: services, value: 'service'},
]}
renderItem={({item}) =>
<View>
<Pressable onPress={() => toggleState(item.key)} style={styles.flatList}>
<Text style={styles.flatListItem}>{item.key}</Text>
<FontAwesome size={20} name={ item.state ? "caret-down" :"caret-left"} style={{color: '#626262'}} />
</Pressable>
{
isExpanded == item.state ?
<View>
<Text>{item.value}</Text>
</View>
: null
}
</View>
}
/>
</View>
)
}
I suspect the issue might be with how I'm managing the isExpanded state. How can I modify my code so that only the selected item is expanded, and the others are collapsed?
Edit: Here is a screenshot of my issue. As you can see, when Channel is selected the arrow icon is pointing down however the contents of the other components are being shown instead!
I eventually figured out how to get one element to toggle at a time. In my FlatList
I just set isExpanded == item.value
in the rather than isExpanded == item.state
. Also in my toggleState
function, I used setExpanded()
to check the current value of isExpanded
before setting it either to false or the value of the switch/case statement.
If there are any better ways to achieve this functionality I would still like to hear and improve on the logic.
export default function CustomHomeDropdown(props: any) {
const { channels, friends, groups, services, setChannels, setFriends, setGroups, setServices } = props
const [ isExpanded , setIsExpanded] = useState(false)
const toggleState = (key: string) => {
switch (key.toLowerCase()) {
case 'channels':
setChannels((prev: any) => !prev);
setIsExpanded(isExpanded === 'channels' ? false : 'channels');
break;
case 'friends':
setFriends((prev: any) => !prev);
setIsExpanded(isExpanded === 'friends' ? false : 'friends');
break;
case 'groups':
setGroups((prev: any) => !prev);
setIsExpanded(isExpanded === 'groups' ? false : 'groups');
break;
case 'services':
setServices((prev: any) => !prev);
setIsExpanded(isExpanded === 'services' ? false : 'services');
break;
default:
break;
}
}
return(
<View style={styles.dropDown}>
<FlatList
data={[
{key: 'Channels', state: channels, value: 'channels'},
{key: 'Friends', state: friends, value: 'friends'},
{key: 'Groups', state: groups, value: 'groups',},
{key: 'Services', state: services, value: 'services'},
]}
renderItem={({item}) =>
<View>
<Pressable onPress={() => toggleState(item.key)} style={styles.flatList}>
<Text style={styles.flatListItem}>{item.key}</Text>
<FontAwesome size={20} name={ item.state ? "caret-down" :"caret-left"} style={{color: '#626262'}} />
</Pressable>
{
isExpanded == item.value ?
<View style={styles.dropDownItem}>
<Text>{item.value}</Text>
</View>
: null
}
</View>
}
/>
</View>
)
}