React Native Arrow Down or Arrow Right When Toggle Collapse - reactjs

I want only my selected FAQ toggle arrow right will be change to arrow down. But for now my code will change All my FAQ arrow right to down when toggle call.
This is my code :
const renderItem = ({ item }, open, show) => {
return (
<View style={styles.container}>
<Collapse onToggle={() => show()}>
<CollapseHeader>
<View style={styles.section}>
<Text style={styles.question}>{item.question}</Text>
{open ? <AntDesign name="down" size={15} color="gray" /> : <AntDesign name="right" size={15} color="gray" />}
</View>
</CollapseHeader>
<CollapseBody>
<Text style={styles.answer}>{item.answer}</Text>
</CollapseBody>
</Collapse>
</View>
);
};
const FAQComponent = () => {
console.log('FAQComponent');
const [open, setOpen] = useState(false);
const show = () => {
setOpen(!open);
}
return (
<FlatList
data={Object.values(FAQData)}
renderItem={(item) => renderItem(item, open, show)}
keyExtractor={item => item.id}
initialNumToRender={10}
/>
);
};
This is the default layout arrow right:
And This is when one of FAQ collapse :
How do I make only selected FAQ when collapse the arrow change to down and the other remain same ?
Thank You.

const renderItem = (item, seletecdItem, show, index) => {
// Instead of Passing a Boolean pass a SelectedItem which holds the index of current selected index and verify it with that
return (
<View style={styles.container}>
<Collapse onToggle={() => show(index)}>
<CollapseHeader>
<View style={styles.section}>
<Text style={styles.question}>{item.question}</Text>
{seletecdItem === index ? (
<AntDesign name="down" size={15} color="gray" />
) : (
<AntDesign name="right" size={15} color="gray" />
)}
</View>
</CollapseHeader>
<CollapseBody>
<Text style={styles.answer}>{item.answer}</Text>
</CollapseBody>
</Collapse>
</View>
);
};
const FAQComponent = () => {
console.log('FAQComponent');
const [selectedItem, setSelectedItem] = useState(-1); // Change the useState to use number
const show = (index) => { // Pass Index through the callback and update the state
setSelectedItem(index);
};
return (
<FlatList
data={Object.values(FAQData)}
renderItem={{ item,index } => renderItem(item, selectedItem, show, index)} // Pass the Index along with renderItem
keyExtractor={item => item.id}
initialNumToRender={10}
/>
);
};

Related

React Native - Passing navigation into a child component

I have a screen with the following:
function Intereset ({ navigation }) {
function ReturnMyFunction () {
if (!var.length) {
return ( <NoILikes /> )
} else {
return (
<FlatList
data={Ilike}
keyExtractor={item => item._id}
ItemSeparatorComponent={() => <Divider />}
renderItem={UserRow}
/>
)
}
}
return ( <ReturnILikeOrNoILike /> )
}
export default Interest
Here is my UserRow component below:
const UserRow = ({ item, navigation }) => (
<TouchableOpacity onPress={() => navigation.navigate("ProfileDetailScreenSingle", { userID: item.likeTo })}>
<View style={styles.row}>
<Image style={styles.avatar}
resizeMode={"cover"}
source={{ uri: item.likeToProfileImage }}
/>
<View style={styles.textContainer}>
<Text style={styles.name}>{item.likeToName}, <Text>{item.likeToAge}</Text></Text>
</View>
<Text style={styles.viewProfileText}>View Profile</Text>
<AntDesign name="right" size={14} color="black" />
</View>
</TouchableOpacity>
)
When I click on the UserRow to navigate I get the following issue.
I'm using useNavigation to redirect screen in stack:
import { useNavigation } from "#react-navigation/native";
const UserRow = ({ item }) => {
const navigation = useNavigation()
const onGoToProfileDetailScreenSingle = () => navigation.navigate("ProfileDetailScreenSingle", { userID: item.likeTo })
return (
<TouchableOpacity onPress={onGoToProfileDetailScreenSingle}>
...
</TouchableOpacity>
);
}
const renderItem = ({ item }) => <UserRow item={item} />
return (
<FlatList
data={Ilike}
renderItem={renderItem}
...
/>
)
Use onPress handler instead and perform navigation on the screen.
const UserRow = ({ item, onPress }) => (
<TouchableOpacity onPress={onPress}>
...
</TouchableOpacity>
);
const renderItem = ({ item }) => {
return (
<UserRow
item={item}
onPress={() => navigation.navigate(...)}
/>
);
};
<FlatList
data={Ilike}
renderItem={renderItem}
...
/>

Invalid use of hooks when calling component with onPress

I'm trying to work with modals when I click on a button from the header.
Say I have this component List, and List is using custom navigation options:
import { CustomModal } from './components/Modal';
const List = (props) => {
const [enteredUrl, setEnteredUrl] = useState('');
const urlInputHandler = (enteredUrl) => {
setEnteredUrl(enteredUrl);
};
const addUrlHander = () => {
console.log(enteredUrl);
}
return (
<View></View>
);
};
List.navigationOptions = (navData) => {
return {
headerTitle: 'Workouts',
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title='Add'
iconName='md-add'
onPress={() => {
CustomModal(); //here is the modal
}}
/>
</HeaderButtons>
),
headerBackTitle: null
};
};
My Modal component has this:
export const CustomModal = (props) => {
const [modalVisible, setModalVisible] = useState(false);
console.log(props);
return (
<Modal
animationType='slide'
transparent={false}
visible={modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}
>
<View style={{ marginTop: 22 }}>
<View>
<Text>Hello World!</Text>
<TouchableHighlight
onPress={() => {
setModalVisible(!modalVisible);
}}
>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
);
}
But it is giving me the invalid hook error. Why is it that my onPress in my navigationOptions giving me this? Am I doing this wrong?
onPress is a callback, you can't put components in it. Probably what you want is something like this:
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<CustomModal/>
</HeaderButtons>
and the modal looks like
export const CustomModal = (props) => {
const [modalVisible, setModalVisible] = useState(false);
console.log(props);
return modalVisible?(
<Modal
animationType='slide'
transparent={false}
visible={modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}
>
<View style={{ marginTop: 22 }}>
<View>
<Text>Hello World!</Text>
<TouchableHighlight
onPress={() => {
setModalVisible(!modalVisible);
}}
>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
):(
<Item
title='Add'
iconName='md-add'
onPress={() => setModalVisible(!modalVisible)}
/>
);
}

Is it possible to prevent a modal from hiding if it's visibility prop rerenders?

I have a component with four different lists of items. An item is placed in a list depending on the date. When the user clicks a button to change the date of that item a modal appears with a date picker (the item's dateModalVisbility prop switches to true). This works as expected but then if an item's date change causes it to switch lists, the date picker modal disappears without the prop switching to false. Then when you click the button to reopen it switches to false and then after another press switches back to true and reopens. I believe this is caused by the rerender of the item when it switches into a different list.
I would like to force the modal to stay open even when it switches lists. I have tried making the different lists functions that I call before anything else renders but this did not solve the issue. I am out of ideas and unsure if this is even possible to do.
My flatlists (this.props.todos comes from redux):
<FlatList
data={_.sortBy(this.props.todos, item => {
return item.date;
})}
extraData={this.props.todos}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => {
if (moment().isSame(item.date, 'day')) {
return (
<TodoItem
todoItem={item}
deleteTodo={() => this.props.removeTodo(item)}
/>
);
}
}}
/>
<View style={styles.headerViewStyle}>
<Text style={styles.headerTextStyle}>Tomorrow</Text>
</View>
<FlatList
data={_.sortBy(this.props.todos, item => {
return item.date;
})}
extraData={this.props.todos}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => {
if (
moment()
.add(1, 'day')
.isSame(item.date, 'day')
) {
return (
<TodoItem
todoItem={item}
deleteTodo={() => this.props.removeTodo(item)}
/>
);
}
}}
/>
<View style={styles.headerViewStyle}>
<Text style={styles.headerTextStyle}>Upcoming</Text>
</View>
<FlatList
data={_.sortBy(this.props.todos, item => {
return item.date;
})}
extraData={this.props.todos}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => {
if (
moment()
.add(1, 'day')
.isBefore(item.date, 'day')
) {
return (
<TodoItem
todoItem={item}
deleteTodo={() => this.props.removeTodo(item)}
/>
);
}
}}
/>
<View style={styles.headerViewStyle}>
<Text style={styles.headerTextStyle}>Sometime</Text>
</View>
<FlatList
data={_.sortBy(this.props.todos, item => {
return item.date;
})}
extraData={this.props.todos}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => {
if (moment().isAfter(item.date, 'day') || item.date === null) {
return (
<TodoItem
todoItem={item}
deleteTodo={() => this.props.removeTodo(item)}
/>
);
}
}}
/>
The todoItem component:
class TodoItem extends Component {
render() {
const todoItem = this.props.todoItem;
return (
<View>
<ItemSwipeRow item={todoItem} completeItem={this.props.deleteTodo}>
<TouchableHighlight
onPress={() => this.props.toggleItemMenu(todoItem)}
underlayColor={null}>
<ListItem
containerStyle={styles.todoItem}
contentContainerStyle={styles.contentStyle}
title={todoItem.text}
titleStyle={{ color: '#FCEFEF', fontSize: 16 }}
rightElement={todoItem.date ? this.renderDate.bind(this)() : null}
/>
</TouchableHighlight>
</ItemSwipeRow>
{todoItem.itemMenuToggled ? <ItemMenuBar item={todoItem} /> : null}
{this.props.reminderToggleActive && todoItem.date ? (
<ReminderToggleButtons item={todoItem} />
) : null}
<NotesModal item={todoItem} />
{todoItem.dateModalVisible ? <DatePickerModal item={todoItem} /> : null}
</View> //this line above is responsible for displaying the date picker modal
);
}
}
And the DatePickerModal:
class DatePickerModal extends Component {
render() {
return (
<Modal transparent animationType="fade" visible>
<View style={styles.containerStyle}>
<View style={styles.modalContainer}>
<View style={{ justifyContent: 'flex-end', flexDirection: 'row' }}>
<View style={{ padding: 5 }}>
<Feather
name="x-square"
size={35}
color={'#db5461'}
onPress={() => this.props.toggleDateModal(this.props.item)}
/>
</View>
</View>
<View style={{ padding: 5 }}>
{Platform.OS === 'ios' ? (
<IosDatePicker item={this.props.item} />
) : (
<AndroidDatePicker />
)}
</View>
</View>
</View>
</Modal>
);
}
}
I can provide the actions and button that opens the item but I don't think that's where the issue is coming from. The button/actions are doing their job right.
I would re-arrange the way you are using the modal. Instead of each component having a modal add one modal to the top level component and then use props to set the items that was selected. Somthing like this:
export default class TopComponent extends React.Component {
this.state = {
selectedItem: null,
modalvisible: false,
}
render = () => {
return (
<FlatList renderItem={({item, key} => (<MyItem onItemSelected={item => this.setState({ selectedItem: item, modalVisible: true}))} />} />
<Modal visible={this.state.modalVisible}>
<Text>this.state.selectedItem</Text>
</Modal>
}
}
Here is a quick codesandbox to show some more details: https://codesandbox.io/s/react-native-6518p

React Native Flatlist header re-rendering when scroll

I am new to React Native and I am having problems with the header in a FlatList.
The header re-renders as soon as I start to scroll, this creates a flickering effect on the images I have in the header.
I have been searching for an answer everywhere but I have not find a posible solution.
¿how could I configure it to stop re-rendering the header when scrolling the list?
....
const Item = ({ title }) => {
return (
<View style={styles.card}>
<Text style={styles.title}>{title}</Text>
</View>
);
};
const listHeader = () => {
const categoriesButtons = categories.map(cat => (
<CategoryButton
text={cat.name}
icon={cat.code}
key={cat.code}
onPress={() => {
//#Todo logic to filter promotions accordingly with the category pressed
}}
/>
));
return (
<View>
<View style={styles.filtersContainer}>
<ImageBackground
source={images.bgShape}
style={{ width: '100%', height: 140 }}
resizeMode="stretch"
>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
{categoriesButtons}
</ScrollView>
</ImageBackground>
</View>
<View style={styles.breadcrumbContainer}>
<Breadcrumbs navigation={navigation} stack={routes} />
</View>
<View style={styles.titleContainer}>
<Text style={sharedStyles.titleText} id="main-title">
¡{totalOfPromotions} promociones activas en Medellín!
</Text>
</View>
</View>
);
};
return (
<LinearGradient
colors={[Colors.BG_START, Colors.BG_END]}
style={styles.mainContainer}
>
{loading ? (
<ActivityIndicator size="large" color="#000000" />
) : (
<FlatList
data={promos}
renderItem={({ item }) => <Item title={item.title} />}
keyExtractor={(item, index) => index.toString()}
ListHeaderComponent={listHeader}
showsVerticalScrollIndicator={false}
onEndReached={showPromos}
onEndThreshold={0.7}
/>
)}
</LinearGradient>
);
};
listHeader() function is being called more than once because in Flatlist tag should be called as
<FlatList
data={promos}
renderItem={({ item }) => <Item title={item.title} />}
keyExtractor={(item, index) => index.toString()}
ListHeaderComponent={listHeader()}
showsVerticalScrollIndicator={false}
onEndReached={showPromos}
onEndThreshold={0.7}
/>
Use () while assigning ListHeaderComponent prop. By this way, function will be invoked only once.
Hope this help you. Enjoy coding!
From what I can see in the code you provided that you are defining the ListHeader component inside your other parent component which will redfine it on every render.
Moving it might outside the parent component might help.
You can fix your flickering issue by memoizing your ListHeaderComponent.
In your case just wrap your component with useMemo:
const listHeader = useMemo(() => {
...
})

useState-hook is not updating my state in an onPress() function

I am building a tab-view and I cannot figure out why the useState-hook is not updating my state. I am sure it is something easy, but I have been stumped here a while now...
I can see that the onPress function is fired and that item.label is the correct one if I log that out. However, setState does not change, even if I hardcode the parameter.
const TabContainer = ({ tabs }) => {
const [selected, setSelected] = useState('');
function renderItem(item, index) {
return item.label === selected ? (
<View style={styles.selectedTab}>
<CustomText style={styles.tabText}>{item.label}</CustomText>
</View>
) : (
<TouchableWithoutFeedback
key={index}
onPress={() => {
console.log(selected);
setSelected(item.label);
}}
>
<View style={styles.tab}>
<CustomText style={styles.tabText}>{item.label}</CustomText>
</View>
</TouchableWithoutFeedback>
);
}
return (
<View style={styles.tabContainer}>
<FlatList
keyExtractor={(item, index) => index.toString()}
data={tabs}
renderItem={({ item, index }) => {
return renderItem(item, index);
}}
style={styles.listContainer}
/>
</View>
);
};
export default TabContainer;
Try this :
<
TouchableWithoutFeedback
key={index}
onPress={() => onPressItem(item)}
>
const onPressItem =(item)=>{
setSelected(item.label)}
Sometimes, using useRef instead of useState when you want to set a value at a different stage in the lifecycle than the constructor.
Please take a look at this and try to implement this in your code:
[https://codesandbox.io/s/v6948pww5y?from-embed]
Okey I kinda solved this. I realized that I had to lift my state up for my tabs in order to render the other stuff properly. So then I just implemented in the same way in the parent component, and then it worked...
Important rows from parent:
const [selectedTab, setSelectedTab] = useState('');
function handleTabPress(tab) {
setSelectedTab(tab);
}
...
<TabContainer
tabs={[
{ label: 'Label 1' },
{ label: 'Label 2' },
{ label: 'Label 3' },
]}
selectedTab={selectedTab}
handleTabPress={handleTabPress}
/>
New child
const TabContainer = ({ tabs, selectedTab, handleTabPress }) => {
function renderItem(item, index) {
return item.label === selectedTab ? (
<View style={styles.selectedTab}>
<CustomText style={styles.selectedTabText}>{item.label}</CustomText>
</View>
) : (
<TouchableWithoutFeedback
key={index}
onPress={() => {
handleTabPress(item.label);
}}
>
<View style={styles.tab}>
<CustomText style={styles.tabText}>{item.label}</CustomText>
</View>
</TouchableWithoutFeedback>
);
}
return (
<View style={styles.tabContainer}>
<FlatList
keyExtractor={(item, index) => index.toString()}
data={tabs}
renderItem={({ item, index }) => {
return renderItem(item, index);
}}
style={styles.listContainer}
/>
</View>
);
};

Resources