Selecting multiple buttons from map function in react native - javascript

I'm trying to select multiple options from the map function but i cant seem to generate logic to do so, i'm selecting a button and that button is generating a heading and a text felid in a scrollview with the lable of button as heading, but now i want to select multiple buttons to generate multiple components:
Any logic and help would be great please, m stuck!
here is code for refrence:
this is usestate and buttonLabels array:
const handlePress = (index) =>
{
setSelectedButtonIndex(index === selectedButtonIndex ? null : index);
};
const buttonLabels = [
'Homeowner',
'In-depth',
'Inherited',
'Probate',
'Lost income',
'Relocation',
'Divorce',
'Downsizing'
];
and here is the map function:
{/* List of Items */}
<View style={styles.buttonGrid}>
{buttonLabels.map((label, index) => (
<TouchableOpacity
key={index}
style={[
styles.button,
{ backgroundColor: index === selectedButtonIndex ? '#346284' : '#FFFFFF' },
]}
onPress={() => handlePress(index)}
>
<Text style={{ color: index === selectedButtonIndex ? '#FFFFFF' : '#888889' }}>
{label}
</Text>
</TouchableOpacity>
))}
</View>
and here i'm generate component at the selectedbutton
<ScrollView style={{flexGrow:2}}>
{selectedButtonIndex !== undefined && (
<View style={styles.headingContainer}>
<View style={styles.heading}>
<Text style={styles.headingText}>{buttonLabels[selectedButtonIndex]}</Text>
<TouchableOpacity onPress={() => setSelectedButtonIndex(undefined)}>
<Image source={require('../assets/Icons/close.png')} style={styles.closeIcon} />
</TouchableOpacity>
</View>
<TextInput style={styles.textInput} placeholder="Enter Text Here" />
</View>
)}
</ScrollView>

if you want to choose multiple item,
let array=[{"option":"option1",selected:false}, {"option":"option1",selected:false}]
based on index or item you need to change the boolean value, then show when the boolean value is true show the item example ,
onpress function like
const onselect=(selectedIndex)=>{
let temp=array //local array
temp.map((item,index)=>{
if(index==selectedIndex) item.selected=!item.selected
})
setState([...temp]) //set your local state
})
in render method,
array.map((item,index)=>{if(item.selected) <View></View>})

Related

Need to update state when number of rows is unknown

I doubt this is possible but I'd like to hear your thoughts.
I am getting an array which the number of values are unknown.
And I am using a for loop to spread it in a state:
const { letters,number } = route.params
const [arr,setArr] = useState([])
useEffect(() => {
var eachArr = []
setArr([])
for (let i = 1; i < Number(number) + 1; i++) {
eachArr.push(i)
}
setArr(eachArr)
}, [])
Now I use the state to map through to render accordingly
return (
<View style={styles.container}>
<ScrollView>
{
arr.length > 0 && arr.map(num => (
<View style={styles.placeContainer} key={num}>
<View style={styles.place}>
<Text style={{ fontWeight:'bold',letterSpacing:1 }}>{num} {letters}</Text>
<Feather name="check-circle" size={24} color="grey" />
</View>
<View style={{ marginVertical:10 }}>
<Progress color={colors.purple} progress={1} width={null} borderWidth={0} />
</View>
<View style={styles.button}>
<Text style={{ color:colors.white,fontFamily:'viga' }}> this {letters}</Text>
</View>
<View style={{...styles.button,backgroundColor:colors.lightPurple}}>
<Text style={{ color:colors.white,fontFamily:'viga' }}> this {letters}</Text>
</View>
</View>
))
}
</ScrollView>
</View>
)
I now have a problem. I have two buttons on each of the mapped element/component as you can see above.
I need to listen for a click on one and deactivate the clicked button and then activate the other button.
The problem is that I do not know how to set/update a state based on the which button is clicked since the number of rows are unknown.
I know I can call a function:
const callFunc = (val) -> {
console.log(val)
}
And call the function when a respective button is clicked and pass the val or sth. While this solves some part of the issue, how do I then deactivate it and active the new button when I didn't set a state for it

How to apply changes to a single element in a Flatlist in React Native

I have a Flatlist that renders multiple posts, each post has a text section, the text can get very big so I'm using a show more method to expand/hide the text, and I'm handling the status of it using a state, however, when I click on a post to expand the text, it expands all posts in the Flatlist, I tried creating a dynamic Ref for each post, but I can't figure out a way to change the text content accordingly, anything I'm missing?
here's my code:
const [showMore, setShowMore] = useState(false);
const refs = useRef([]);
// Inside a Flatlist render item:
<View style={styles.postContainer}>
{item.data.postText.length > 120 ? (
showMore ? (
<TouchableOpacity onPress={() => setShowMore(!showMore)}
ref={(expandableText) => (refs.current[index] = expandableText)}>
<Text style={styles.postDescription}>{item.data.postText}</Text>
<Text style={styles.seeMore}>Show less</Text>
</TouchableOpacity>
) : (
<TouchableOpacity onPress={() => setShowMore(!showMore)}>
<Text style={styles.postDescription}>
{`${item.data.postText.slice(0, 120)}... `}
</Text>
<Text style={styles.seeMore}>Show more</Text>
</TouchableOpacity>
)
) : (
<Text style={styles.postDescription}>{item.data.postText}</Text>
)}
</View>
You are using the same state for all items of the FlatList. Hence, if you change the state, all items will be expanded. You could keep a boolean array as a state. The index of this state array corresponds to the index of a component inside the flatlist.
// data is the data of your FlatList
// we use this to initialize each show more value with false
const [showMore, setShowMore] = useState(data.map(data => false))
In your render function you use it as follows.
renderItem={({item, index}) => {
return <View style={styles.postContainer}>
{item.data.postText.length > 120 ? (
showMore[index] ? (
<TouchableOpacity onPress={() => handleShowMore(index)}
ref={(expandableText) => (refs.current[index] = expandableText)}>
<Text style={styles.postDescription}>{item.data.postText}</Text>
<Text style={styles.seeMore}>Show less</Text>
</TouchableOpacity>
) : (
<TouchableOpacity onPress={() => handleShowMore(index)}>
<Text style={styles.postDescription}>
{`${item.data.postText.slice(0, 120)}... `}
</Text>
<Text style={styles.seeMore}>Show more</Text>
</TouchableOpacity>
)
) : (
<Text style={styles.postDescription}>{item.data.postText}</Text>
)}
</View>
}
The handleShowMore function is as follows.
function handleShowMore(index) {
setShowMore(prev => prev.map((element, idx) => {
if(idx === index) {
return !element
}
return element
}))
}

React Native FlatList: render conditional style with dynamic data

I am new to React native and implementing FlatList where I am facing some issues with dynamic data. I have a list of total seats and booked seats.
this.state = {
seats: props.route.params.seats,
reservedSeats: props.route.params.Reserved,
date: new Date()
}
Following is the FlatList i have implemented
<FlatList style={styles.flatListArea1}
contentContainerStyle={{margin:0}}
data={this.state.seats}
numColumns={4}
showsHorizontalScrollIndicator={false}
renderItem={({item}) =>
<View style={styles.containerIcons} key={item}>
<TouchableOpacity style={this.state.selectedItem === item ? styles.menuSelected : styles.menuTop } onPress={ () => this.selectSeat(item)}>
<View style={styles.buttons}>
<Text style={styles.HallsText} key={item.key}>{item.Id}</Text>
</View>
</TouchableOpacity>
</View>}
/>
On Click event, I am able to change the color. I appreciate if someone can help to understand, How we can re-render FlatList based on dynamic data presented in reserved state. For example. for today, out of 5 seats 3 are booked. Those should be gray and others should be red.
I appreciate your help on this.
Regards,
You firstly need a method that tells if a seat is available or not.
isReserved = (item) => {
return this.state.reservedSeats.filter(seat => seat.Id ==item.Id).length > 0;
}
Then you change the appearance of your list like this
<FlatList
style={styles.flatListArea1}
contentContainerStyle={{ margin: 0 }}
data={this.state.seats}
numColumns={4}
showsHorizontalScrollIndicator={false}
renderItem={({ item, index }) => (
<View style={[styles.containerIcons, { backgroundColor: isReserved(item) ? "#FAFAFA" : "white" }]} key={index}>
<TouchableOpacity
style={this.state.selectedItem === item ? styles.menuSelected : styles.menuTop}
onPress={() => this.selectSeat(item)}
disabled={isReserved(item)}
>
<View style={styles.buttons}>
<Text
style={[styles.HallsText, { backgroundColor: isReserved(item) ? "#CCC" : "white" }]}
key={item.key}
>
{item.Id}
</Text>
</View>
</TouchableOpacity>
</View>
)}
/>;
Pay attention to the key attribute

Property not being added to object

I have a object which has an array inside it. Each array has multiple objects. So using the .map function I am looping over the array inside the object. Each array item has a click function where I toggle the item. Initial state of the array item is this
{
DisplayText: "Foresatte"
Selectable: true
UserRecipientToInclude: "IncludeGuardianRecipient"
}
When I do this
choice.Selected = false
and console logs the result the newly added item is not present in the object. Why is that?
Here is the code
{c.UserDropdownMenuChoices.map(choice => {
return ( <TouchableHighlight
{...testProperties(choice.DisplayText, true)}
style={{ opacity: !choice.Selectable ? 0.4 : 1 }}
onPress={() => {
this.selectChoice(c.UserDropdownMenuChoices, choice, c)
}}
underlayColor={'transparent'}
key={choice.DisplayText}
>
<View style={styles.choiceContainer}>
{
choice.selected ? (
//<MaterialCommunityIcons name={'checkbox-marked'} style={styles.checkboxClick} size={20} />
<Icon
type={'material-community'}
name={'checkbox-marked'}
iconStyle={styles.checkboxClick}
size={20}
/>
) : (
<Icon
type={'material-community'}
name={'checkbox-blank-outline'}
iconStyle={styles.checkboxDefault}
size={20}
/>
)
//DONE: <MaterialCommunityIcons name={'checkbox-blank-outline'} style={styles.checkboxDefault} size={20} />
}
<Text style={styles.displayText}>{choice.DisplayText}</Text>
</View>
</TouchableHighlight> )}
and my function is like this
selectChoice(choicesList, choice, contact) {
choice.selected = true
...
console.log(choice) // doesn't have the new property
}
This code is for a react native application
I have previously solved a similar issue by simply scoping out the select hook inside the .map without mapping the attribute onto the array itself.
So what I did was:
import MenuRow from "./menu_row"
...
const Rooms = allRooms.map((room, index) => {
return (
<MenuRow key={room.id} room={room}/>
)
})
Inside MenuRow.js
const MenuRow = (props) => {
let room = props.room
const [selected, setSelected] = useState(true) // Checked by default
return (
<TouchableOpacity onPress={() => {setSelected(!selected), applySelected(room.id, selected) }} style={s.checkrow}>
...
{selected ?
// SELECTED
<View style={s.checked}></View>
:
// NOT SELECTED
<View style={s.unchecked}></View>
}
</TouchableOpacity>
)
However you could also give this a try:
https://stackoverflow.com/a/44407980/4451733

Show Empty list message only after Loader get ended in React Native using Flat list

I have a flat list, which gets its data source as a state. Actually, this data is from firebase, and i have been using redux. So, the data is fetched in the actions, and using callback i get the data to state.
What i want to achieve is, when there is no data found from the api, An empty list message should be show in the view. Actually , i achieved this using "ListEmptyComponent". But whats happening is the screen starts with empty message, and the spinner loads below it, and then if data found the message goes away as well as spinner.
But, what i wanted is, when the view gets rendered the first thing everyone should see is the spinner, and then if data empty spinner hides then empty list message displays.
How to achieve this ?
My Action :
export const fetchOrderHistory = (phone, callback) => {
return (dispatch) => {
dispatch({ type: START_SPINNER_ACTION_FOR_ORDER_HISTORY })
firebase.database().ref('orders/'+phone)
.on('value', snapshot => {
const snapShotValue = snapshot.val();
callback(snapShotValue);
dispatch ({ type: ORDER_HISTORY_FETCHED , payload: snapshot.val()});
dispatch({ type: STOP_SPINNER_ACTION_FRO_ORDER_HISTORY })
});
};
};
My Flat List & spinner:
<FlatList
data={this.state.historyOfOrders}
keyExtractor={item => item.uid}
ListEmptyComponent={this.onListEmpty()}
renderItem={({ item }) => (
<Card
containerStyle={{ borderRadius: 5 }}
>
<View style={styles.topContainerStyle}>
<View>
<TouchableOpacity
onPress={() => this.props.navigation.navigate('ViewOrderScreen', {itemsOfOrder: item}) }
>
<View style={styles.viewOrderContainer}>
<View style={styles.viewOrderTextContainer}>
<Text style={styles.viewOrderTextStyle}>View Order</Text>
</View>
<Icon
name='ios-arrow-forward'
type='ionicon'
color='#ff7675'
/>
</View>
</TouchableOpacity>
</View>
</View>
</View>
</Card>
)}
/>
{this.props.isSpinnerLoading &&
<View style={styles.loading}>
<ActivityIndicator size="large" color="#03A9F4"/>
</View> }
My Call back at componentWillMount which set state:
componentWillMount() {
this.props.fetchOrderHistory((this.props.phone), (snapShotValue)=> {
const userOrderHistory = _.map(snapShotValue, (val,uid) => ({uid, ...val}))
this.setState({ historyOfOrders: userOrderHistory })
});
}
My EmptyList Message:
onListEmpty = () => {
return <View style={{ alignSelf: 'center' }}>
<Text style={{ fontWeight: 'bold', fontSize: 25 }}>No Data</Text>
</View>
}
My State:
state = { historyOfOrders: "" }
I am getting the spinner values from the reducers, using mapStateToProps.
Kindly Guide me, through
you have to do two things for that.
First, show Flatlist only if the loader is stopped. Second, set default value of this.state.historyOfOrders is null and check if this.state.historyOfOrders not null then only show Flatlist.
Here is a code:
{(!this.props.isSpinnerLoading && this.state.historyOfOrders != null) ?
(
<FlatList
data={this.state.historyOfOrders}
keyExtractor={item => item.uid}
ListEmptyComponent={this.onListEmpty()}
renderItem={({ item }) => (
<Card containerStyle={{ borderRadius: 5 }}>
<View style={styles.topContainerStyle}>
<View>
<TouchableOpacity onPress={() => this.props.navigation.navigate('ViewOrderScreen', {itemsOfOrder: item}) }>
<View style={styles.viewOrderContainer}>
<View style={styles.viewOrderTextContainer}>
<Text style={styles.viewOrderTextStyle}>View Order</Text>
</View>
<Icon
name='ios-arrow-forward'
type='ionicon'
color='#ff7675'
/>
</View>
</TouchableOpacity>
</View>
</View>
</Card>
)}
/>
) : null
}
With this condition, even if you want loader above Flatlist you can do that.
The path you should take is rendering only the spinner when the loading flag is set and rendering the list when loading flag is false.
Your render method should be like below
render()
{
if(this.props.isSpinnerLoading)
{
return (<View style={styles.loading}>
<ActivityIndicator size="large" color="#03A9F4"/>
</View> );
}
return (/** Actual List code here **/);
}

Categories