I am using react-native-bouncy-checkbox
and Flatlist
.
I have created an array object which has
id
,name
,amount
.So far I have achieved:
User can select individual items from the
Flatlist
, and it will add theamount
and display it as total amount.User can also edit the
amount
they have selected using TextInput.
However, I am trying to create a 'Select All' feature.
So, when user presses 'Select All' or taps on the 'checkbox' it should:
- select all the items from the FlatList
- add the total amount
- allow user to edit all the selected amount individually
- update the checkbox to show that it is selected.
So far I have tried getting all the 'checkbox' to show that it is selected when 'Select All' text is pressed or when 'checkbox' is pressed (beside select all text).
I have been trying to get this to work for the last couple of hours but couldn't manage to do it. So any help regarding this issue are most welcome.
CODE SNIPPETS AND APP SCREENSHOTS PROVIDED BELOW:
Code Sample:
import 'react-native-gesture-handler';
import React, { useState, useEffect, Component } from 'react';
import { StyleSheet, View, Text, TouchableOpacity, FlatList } from 'react-native';
import { Button, Divider, TextInput } from 'react-native-paper';
import BouncyCheckbox from 'react-native-bouncy-checkbox';
import TextInputMask from 'react-native-text-input-mask';
function AccountMultiplePayment({ navigation }) {
const [apiData, setApiData] = useState([
{
id: 1,
name: 'John',
address: 'address 1',
amount: '79.90',
},
{
id: 2,
name: 'Simon',
address: 'address 2',
amount: '35.50',
},
{
id: 3,
name: 'Tim',
address: 'address 3',
amount: '15.50',
},
{
id: 4,
name: 'Rob',
address: 'address 4',
amount: '33.33',
},
{
id: 5,
name: 'Sarah',
address: 'address 5',
amount: '77.77',
},
])
const [billPaymentAmount, setBillPaymentAmount] = useState({})
const [selectedBill, setSelectedBill] = useState([]);
const [totalPaymentAmount, setTotalPaymentAmount] = useState(0);
const computeBillPaymentAmount = () => {
let newBillPaymentAmount = {}
apiData.forEach(({ id, amount }) => {
newBillPaymentAmount[id] = amount
})
return newBillPaymentAmount
}
const computeTotalPaymentAmount = () => {
let total = 0
selectedBill.forEach(id => {
total = parseFloat(billPaymentAmount[id])
})
// Prevent NaN issue, because once user delete amount will become empty string
return total ? total : 0
}
useEffect(() => {
setBillPaymentAmount(computeBillPaymentAmount())
}, [apiData])
useEffect(() => {
setTotalPaymentAmount(computeTotalPaymentAmount())
}, [selectedBill, billPaymentAmount])
const [checked, setChecked] = useState(false);
return (
<>
<View style={{flexDirection: 'row'}}>
<TouchableOpacity
style={{ alignItems: 'center', paddingVertical: 10 }}
onPress={() => setChecked(!checked)}
>
<Text style={{ color: 'black', fontSize: 25 }}>Select All</Text>
</TouchableOpacity>
<BouncyCheckbox
isChecked={checked}
fillColor={'green'}
unfillColor={'#FFFFFF'}
onPress={() => {
setChecked(!checked)
}}
/>
</View>
<FlatList
data={apiData}
renderItem={({ item }) => {
return (
<View style={{ flexDirection: 'row' }}>
<View style={[styles.subHeaderContainer, { flex: 1 }]}>
<Text
style={[
styles.defaultText,
{ fontWeight: 'bold', fontSize: 16 },
]}>
{item.name}
</Text>
<Divider style={{ marginVertical: 5 }} />
<View style={{ flexDirection: 'row' }}>
<Text
style={[styles.defaultText, { fontWeight: 'bold', flex: 2 }]}>
Total Payable Amount:
</Text>
<View style={{ flex: 1 }}>
<TextInput
value={billPaymentAmount[item.id]}
onChangeText={value => setBillPaymentAmount({ ...billPaymentAmount, [item.id]: value })}
keyboardType={'numeric'}
mode={'outlined'}
label={'RM'}
dense={true}
render={props =>
<TextInputMask
{...props}
mask='[9990].[99]'
/>
}
/>
</View>
</View>
</View>
<BouncyCheckbox
isChecked={checked}
fillColor={'green'}
unfillColor={'#FFFFFF'}
onPress={() => {
if (selectedBill.includes(item.id)) {
setSelectedBill(selectedBill.filter(value => value !== item.id))
} else {
setSelectedBill([...selectedBill, item.id])
}
}}
/>
</View>
);
}}
keyExtractor={item => item.id}
removeClippedSubviews={false}
/>
{
<>
<View
style={{
paddingVertical: 10,
paddingHorizontal: 20,
flexDirection: 'row',
backgroundColor:'blue'
}}>
<Text
style={{ color: 'white', flex: 1, fontWeight: 'bold', fontSize: 18 }}>
Total Amount:{' '}
</Text>
<View>
<Text style={{ color: 'white', fontWeight: 'bold', fontSize: 24 }}>
RM {totalPaymentAmount.toFixed(2)}
</Text>
{totalPaymentAmount <= 0 ? null : (
<TouchableOpacity
onPress={() => {
//navigation.goBack();
navigation.goBack();
navigation.navigate('Account');
}}>
<Text>Reset</Text>
</TouchableOpacity>
)}
</View>
</View>
<Button
mode={'contained'}
color={'limegreen'}
style={{
borderRadius: 5,
marginHorizontal: 20,
marginVertical: 10,
justifyContent: 'center',
}}
labelStyle={{ color: 'white', padding: 10 }}
uppercase={false}
onPress={() => { }}
disabled={totalPaymentAmount <= 0 ? true : false}>
<Text>Pay Bill</Text>
</Button>
</>
}
</>
);
}
class Account extends Component {
constructor(props) {
super(props);
this._isMounted = false;
this.state = {
};
}
render() {
return (
<>
{<AccountMultiplePayment {...this.props} {...this.navigation} />}
</>
);
}
}
export default Account;
const styles = StyleSheet.create({
flex: {
flex: 1,
},
headerTitle: {
alignItems: 'center',
borderBottomLeftRadius: 25,
borderBottomRightRadius: 25,
paddingHorizontal: 20,
paddingVertical: 20,
},
subHeaderContainer: {
paddingVertical: 10,
paddingHorizontal: 20,
backgroundColor: 'white',
borderRadius: 10,
elevation: 5,
marginVertical: 5,
marginHorizontal: 10,
},
subHeaderTitle: {
color: 'white',
fontWeight: 'bold',
fontSize: 16,
backgroundColor: '#2c1855',
padding: 10,
borderRadius: 10,
},
defaultText: {
color: 'black',
},
});
This is what it looks like currently. All checked items are individually selected:
This is what I am trying to achieve:
CodePudding user response:
Pressing Select All
yields
Since you want to calculate additional values (total payable amount for each selected item) I would suggest to just add the checks
state to your already implemented useEffect
. When the checks
state changes, this useEffect
will be called. You can calculate all fields for which the boolean flag inside checks
is true and set the state for the input fields.
CodePudding user response:
Let refactor code as below
import "react-native-gesture-handler";
import React, { useState, useEffect, Component } from "react";
import {
StyleSheet,
View,
Text,
TouchableOpacity,
FlatList,
} from "react-native";
import { Button, Divider, TextInput } from "react-native-paper";
import BouncyCheckbox from "react-native-bouncy-checkbox";
function AccountMultiplePayment({ navigation }) {
const [apiData, setApiData] = useState([
{
id: 1,
name: "John",
address: "address 1",
amount: "79.90",
},
{
id: 2,
name: "Simon",
address: "address 2",
amount: "35.50",
},
{
id: 3,
name: "Tim",
address: "address 3",
amount: "15.50",
},
{
id: 4,
name: "Rob",
address: "address 4",
amount: "33.33",
},
{
id: 5,
name: "Sarah",
address: "address 5",
amount: "77.77",
},
]);
const [billPaymentAmount, setBillPaymentAmount] = useState({});
const [selectedBill, setSelectedBill] = useState([]);
const [totalPaymentAmount, setTotalPaymentAmount] = useState(0);
const computeBillPaymentAmount = () => {
let newBillPaymentAmount = {};
apiData.forEach(({ id, amount }) => {
newBillPaymentAmount[id] = amount;
});
return newBillPaymentAmount;
};
const computeTotalPaymentAmount = () => {
let total = 0;
selectedBill.forEach((id) => {
total = parseFloat(billPaymentAmount[id]);
});
// Prevent NaN issue, because once user delete amount will become empty string
return total ? total : 0;
};
useEffect(() => {
setBillPaymentAmount(computeBillPaymentAmount());
}, [selectedBill.length]);
useEffect(() => {
setTotalPaymentAmount(computeTotalPaymentAmount());
}, [billPaymentAmount]);
const selectAllBill = () => {
if (selectedBill.length < apiData.length) {
setSelectedBill([...new Set(apiData.map((item) => item.id))]);
}
if (selectedBill.length === apiData.length) {
setSelectedBill([]);
}
};
const isBillAdded = (id) => selectedBill.some((el) => el === id);
const hasAllBillselected = apiData.length === selectedBill.length;
return (
<>
<View style={{ flexDirection: "row" }}>
<TouchableOpacity
style={{ alignItems: "center", paddingVertical: 10 }}
onPress={selectAllBill}
>
<Text style={{ color: "black", fontSize: 25 }}>Select All</Text>
</TouchableOpacity>
<BouncyCheckbox
disableBuiltInState
isChecked={hasAllBillselected}
fillColor={"green"}
unfillColor={"#FFFFFF"}
onPress={selectAllBill}
/>
</View>
<FlatList
data={apiData}
renderItem={({ item }) => {
return (
<View style={{ flexDirection: "row" }}>
<View style={[styles.subHeaderContainer, { flex: 1 }]}>
<Text
style={[
styles.defaultText,
{ fontWeight: "bold", fontSize: 16 },
]}
>
{item.name}
</Text>
<Divider style={{ marginVertical: 5 }} />
<View style={{ flexDirection: "row" }}>
<Text
style={[
styles.defaultText,
{ fontWeight: "bold", flex: 2 },
]}
>
Total Payable Amount:
</Text>
<View style={{ flex: 1 }}>
<TextInput
value={billPaymentAmount[item.id]}
onChangeText={(value) =>
setBillPaymentAmount({
...billPaymentAmount,
[item.id]: value,
})
}
keyboardType={"numeric"}
mode={"outlined"}
label={"RM"}
dense={true}
/>
</View>
</View>
</View>
<BouncyCheckbox
disableBuiltInState
isChecked={selectedBill.includes(item.id)}
fillColor={"green"}
unfillColor={"#FFFFFF"}
onPress={() => {
if (selectedBill.includes(item.id)) {
setSelectedBill(
selectedBill.filter((value) => value !== item.id)
);
} else {
setSelectedBill([...new Set([...selectedBill, item.id])]);
}
}}
/>
</View>
);
}}
keyExtractor={(item) => item.id}
removeClippedSubviews={false}
/>
{
<>
<View
style={{
paddingVertical: 10,
paddingHorizontal: 20,
flexDirection: "row",
backgroundColor: "blue",
}}
>
<Text
style={{
color: "white",
flex: 1,
fontWeight: "bold",
fontSize: 18,
}}
>
Total Amount:{" "}
</Text>
<View>
<Text
style={{ color: "white", fontWeight: "bold", fontSize: 24 }}
>
RM {totalPaymentAmount.toFixed(2)}
</Text>
{totalPaymentAmount <= 0 ? null : (
<TouchableOpacity
onPress={() => {
//navigation.goBack();
navigation.goBack();
navigation.navigate("Account");
}}
>
<Text>Reset</Text>
</TouchableOpacity>
)}
</View>
</View>
<Button
mode={"contained"}
color={"limegreen"}
style={{
borderRadius: 5,
marginHorizontal: 20,
marginVertical: 10,
justifyContent: "center",
}}
labelStyle={{ color: "white", padding: 10 }}
uppercase={false}
onPress={() => {}}
disabled={totalPaymentAmount <= 0 ? true : false}
>
<Text>Pay Bill</Text>
</Button>
</>
}
</>
);
}
class Account extends Component {
constructor(props) {
super(props);
this._isMounted = false;
this.state = {};
}
render() {
return (
<>{<AccountMultiplePayment {...this.props} {...this.navigation} />}</>
);
}
}
export default Account;
const styles = StyleSheet.create({
flex: {
flex: 1,
},
headerTitle: {
alignItems: "center",
borderBottomLeftRadius: 25,
borderBottomRightRadius: 25,
paddingHorizontal: 20,
paddingVertical: 20,
},
subHeaderContainer: {
paddingVertical: 10,
paddingHorizontal: 20,
backgroundColor: "white",
borderRadius: 10,
elevation: 5,
marginVertical: 5,
marginHorizontal: 10,
},
subHeaderTitle: {
color: "white",
fontWeight: "bold",
fontSize: 16,
backgroundColor: "#2c1855",
padding: 10,
borderRadius: 10,
},
defaultText: {
color: "black",
},
});
Working example here - https://snack.expo.dev/@emmbyiringiro/971a0c