0

I have a flatlist in my application and I want to enable a user to mark a particular list item as selected on Long press and provide a delete button to remove multiple items in one go. These are the behaviors I expect.


  1. If no items in the flatlist are selected, pressing an item opens a new screen with item details and back button takes you back to the flatlist.
  2. If no items are selected, long pressing an item marks it as selected. Every item pressed after any particular item is selected is marked as selected instead of opening the details screen.
  3. Items which are already selected and then pressed become unselected.
  4. If any number of items are selected, a delete button is rendered and pressing the back button unselects all items.

I have been able to achieve most of the first three behaviors but I am completly lost with the Back Handler. Here is my component with only the relevant code for brevity. Only the state array which contains the items selected for deletion and the listItem which is used as the RenderItem prop for the flatlist is shown.

const Home = (props) => {
    const [deleteItems, setDeleteItems] = useState([]);
    const renderItem = ({ item }) => {
    let bb_OR_ub = item.useBy ? 'Use By ' : 'Best Before '
    let exp_check = CheckExp(item, 1);
    let listStyle = {};
    if (exp_check === -1)
      listStyle = { backgroundColor: '#ff9ea5' }
    else if (exp_check === 0)
      listStyle = { backgroundColor: '#fff185' }
    if (deleteItems.indexOf(item.name) != -1) {
      listStyle = { opacity: 0.3 }
    }
    return (
      <ListItem
        containerStyle={listStyle}
        badge={
          exp_check !== 1 ?
            exp_check === -1 ? { status: 'error', value: `!` } : {
              status: 'warning'
            } : null
        }
        title={item.name}
        subtitle={bb_OR_ub + item.date}
        bottomDivider
        leftAvatar={{ source: require('../shared/cexp.png'), imageProps: { resizeMode: 'contain' } }}
        onPress={() => {
          if (deleteItems.length == 0)
            navigate('ExpiryDetails', { item })
          else {
            setDeleteItems([...deleteItems, item.name])
          }
        }}
        onLongPress={() => {
          if (deleteItems.indexOf(item.name) == -1 || deleteItems.length == 0) {
            setDeleteItems([...deleteItems, item.name])
          }
          else {
            setDeleteItems(deleteItems.filter(el => el != item.name))
          }
        }} />
    );
  }
Harsha Limaye
  • 915
  • 9
  • 29

1 Answers1

1

The BackHandler provided by react-native allows you to subscribe to back button being pressed. The callback provided to backhandler can provide either true (when the default behavior should not trigger) or false (when the default behavior is allowed to continue).

For your case, we want to have custom behavior on back when there are items selected, at that moment we want to unselect all items.

I adjusted your code to introdcue the BackHandler and unselect any items on back being pressed

const Home = (props) => {
    const [deleteItems, setDeleteItems] = useState([]);

    // Subscribe to BackHandler once the component is mounted
    // or when deletedItems changes
    useEffect(() => {
      const handler = BackHandler.addEventListener('hardwareBackPress', () => {
        // If no deleted items: we return false
        if (!deletedItems.length) {
          return false;
        }

        // clear the selected items, and indicate that the back if handled
        setDeletedItems([]);
        return true;
      });

      // unsubscribe when component unmounts
      return () => {
        handler.remove();
      };
    }, [deletedItems]);

    const renderItem = ({ item }) => {
    let bb_OR_ub = item.useBy ? 'Use By ' : 'Best Before '
    let exp_check = CheckExp(item, 1);
    let listStyle = {};
    if (exp_check === -1)
      listStyle = { backgroundColor: '#ff9ea5' }
    else if (exp_check === 0)
      listStyle = { backgroundColor: '#fff185' }
    if (deleteItems.indexOf(item.name) != -1) {
      listStyle = { opacity: 0.3 }
    }
    return (
      <ListItem
        containerStyle={listStyle}
        badge={
          exp_check !== 1 ?
            exp_check === -1 ? { status: 'error', value: `!` } : {
              status: 'warning'
            } : null
        }
        title={item.name}
        subtitle={bb_OR_ub + item.date}
        bottomDivider
        leftAvatar={{ source: require('../shared/cexp.png'), imageProps: { resizeMode: 'contain' } }}
        onPress={() => {
          if (deleteItems.length == 0)
            navigate('ExpiryDetails', { item })
          else {
            setDeleteItems([...deleteItems, item.name])
          }
        }}
        onLongPress={() => {
          if (deleteItems.indexOf(item.name) == -1 || deleteItems.length == 0) {
            setDeleteItems([...deleteItems, item.name])
          }
          else {
            setDeleteItems(deleteItems.filter(el => el != item.name))
          }
        }} />
    );
  }
Thomas
  • 7,933
  • 4
  • 37
  • 45
  • I did somethig similar and then ran into another related problem. The function handler belongs to one particular render when the component mounts and I was always getting the stale state. (No items selected.) Now I will try adding deletedItems to the dependency array of useEffect OR use a ref to get the latest state value into my handler. Cheers. – Harsha Limaye Oct 07 '20 at 21:25
  • https://stackoverflow.com/questions/53845595/wrong-react-hooks-behaviour-with-event-listener Related problem where event listener gets the stale state. – Harsha Limaye Oct 07 '20 at 21:25