Need to update state when number of rows is unknown - javascript

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

Related

Selecting multiple buttons from map function in react native

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>})

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 Modal is not hiding

I am implementing my own Modal, trying to replace the Alert.alert with something more beautiful. I made it to be displayed when needed, but it is not hiding on the button press, but I think I transferred it the needed function. My modal structure is the following:
export const RCModal = ({ title, visible, onButtonPress }) => {
return (
<Modal
animationType='fade'
transparent={true}
visible={visible}
>
<View style={styles.container}>
<Text style={styles.title}>{title}</Text>
<Pressable style={styles.button} onPress={onButtonPress}>
<Text style={styles.text}>OK</Text>
</Pressable>
</View>
</Modal>
)
};
And it is used in the application in the following way:
// ...
const [alertVisible, setAlertVisible] = useState(false);
const [alertTitle, setAlertTitle] = useState();
const [alertOnPress, setAlertOnPress] = useState();
// ...
const winner = (theWinner) => {
setBlocked(true);
setAlertTitle(`${theWinner} win!`);
setAlertOnPress(() => setAlertVisible(!alertVisible));
setAlertVisible(true);
}
// ...
return (
<View style={styles.container}>
<RCModal title={alertTitle} visible={alertVisible} onButtonPress={alertOnPress} />
<ScrollView contentContainerStyle={{ flexGrow: 1, justifyContent: 'center' }}>
<Text style={styles.title}>Noughts and Crosses</Text>
<Text style={styles.tip}>Get {winCondition()} in row, column or diagonal</Text>
<View style={styles.buttonsContainer}>
<Text style={styles.turnContainer}>Turn: <Text style={[styles.turn, { color: turn === 'X' ? '#2E86C1' : '#E74C3C'}]}>{turn}</Text></Text>
<TouchableHighlight underlayColor="#000000" style={[styles.button, styles.newGameButton]} onPress={setInitialFieldState}>
<Text style={styles.buttonText}>New game</Text>
</TouchableHighlight>
</View>
<Field state={fieldState} size={fieldSize} onCellPress={onCellPress} />
</ScrollView>
<View style={styles.settingsButtonContainer}>
<TouchableHighlight underlayColor={theme.colors.secondary} style={styles.settingsButton} onPress={onSettingsPress}>
<Image source={require('../img/settings.png')} style={styles.settingsIcon} />
</TouchableHighlight>
</View>
</View>
);
};
When the winner() is called, it is displayed as it should, but when I press OK button, it is not hiding. How can I fix it?
You can use setAlertVisible to change the alertVisible state:
<RCModal title={alertTitle} visible={alertVisible} onButtonPress={() => setAlertVisible(false)} />
The answer was that to set a function like a state variable, I needed to set it like
setAlertOnPress(() => () => setAlertVisible(false))
(2 x () =>)

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 **/);
}

How to change design(e.g color) of button on press which is rendered through loop?

Hey guys can anybody help me way out of this problem. My problem is related to improving UX of the app. I have rendered accordion through a loop which renders data with a button on it. I wanted to change color of the button which is pressed thus helping in UX. But ran into problem as all buttons are turning their colors . The problem is i am rendering color from state can anybody help me out with some different solution.
This is the loop through which i am rendering accordion:
{this.state.services.map((tab, i) => {
return(
<ScrollView key={i} tabLabel={tab.category} style={{flex: 1}}>
{tab.names.map((name, j) => this.renderRow(name))}
</ScrollView>
)
})}
This is the render function:
renderRow (rowData) {
var header = (
rowData.subGroup.length > 1 ? <View>
<Text>{rowData.name}</Text>
</View>
:
<View>
<View style={{flex: 1}}>
<Text>{rowData.name}</Text>
<Text>{rowData.subGroup[0].duration}Min</Text>
</View>
<Text>₹{rowData.subGroup[0].price.M ? rowData.subGroup[0].price.M : rowData.subGroup[0].price.F}</Text>
<TouchableOpacity onPress={() => this._add(rowData.subGroup[0])}>
<View style={styles.buttonView}>
<Text style={styles.buttonText}>ADD</Text>
</View>
</TouchableOpacity>
</View>
);
var content = (
<View style={{flex: 1}}>
{rowData.subGroup.map((sg, i) => {
return(
sg.subGroup ? <View>
<View style={{flex: 1}}>
<Text>{sg.subGroup ? sg.subGroup : sg.name}</Text>
<Text>{sg.duration}Min</Text>
</View>
<Text style={styles.price}>₹{sg.price.M ? sg.price.M : sg.price.F}</Text>
<TouchableOpacity onPress={() => this._add(sg)}>
<View style={styles.buttonView}>
<Text style={styles.buttonText}>ADD</Text>
</View>
</TouchableOpacity>
</View>
:
<View></View>
)
})}
</View>
);
return (
<Accordion
key={rowData.name}
header={header}
content={content}
easing="easeOutCubic"
/>
);
}
can anybody guide me through different approach?
Guys i found out the solution just wrote a simple function and returned true or false. Based on the returned value and ternary operator i change style of selected value
here is a solution:
<View style={this._toggleServiceButtonView(sg) ? styles.removeView : styles.buttonView}>
<Text style={this._toggleServiceButtonView(sg) ? styles.removeText : styles.buttonText}>
{this._toggleServiceButtonView(sg) ? 'REMOVE' : 'ADD'}
</Text>
</View>
Here is the function in which i am just checking if the selected value is in cart array to return true.
_toggleServiceButtonView(data) {
return this.state.cartArr.length != 0 && this.state.cartArr.findIndex((service) => {
if(service.service._id === data._id){
return true;
} else {
return false;
}
}) !== -1
}

Categories