Home > Enterprise >  React Native FlatList : Select all Items and run 'OnPress'
React Native FlatList : Select all Items and run 'OnPress'

Time:02-22

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 the amount 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:

  1. select all the items from the FlatList
  2. add the total amount
  3. allow user to edit all the selected amount individually
  4. 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:

current app screenshot


This is what I am trying to achieve:

goal for this app

CodePudding user response:

After checking the enter image description here

Pressing Select All yields

enter image description here

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

  • Related