fashion game
fashion game

Reputation: 1

react-native re-render negates filter

To learn myself react-native I am building an app that contains a FlatList filled with tickets with help of redux. When I try to filter through the tickets by typing in a number, the list gets filtered but only for 1 second. After that it gives a list of all tickets again. I have trouble finding the the logical error behind my beginner code. Any help would be appreciated.

I pasted the list below:

const AllTicketList = ({ navigation, ticket: { allTickets }, getTickets }) => {
  useEffect(() => {
    getTickets();
  }, []);

  const [enteredValue, setEnteredValue] = useState();
  const [selectedNumber, setSelectedNumber] = useState(false);
  const [displayedTickets, setDisplayedTickets] = useState();
  const [confirmed, setConfirmed] = useState(false);

  useEffect(() => {
    setDisplayedTickets(allTickets);
  });

  const confirmInputHandler = () => {
    const chosenNumber = parseInt(enteredValue);
    if (isNaN(chosenNumber) || chosenNumber <= 0) {
      Alert.alert(
        'Invalid number',
        'The number of upvotes has to be greater than 0.',
        [{ text: 'Ok', style: 'destructive', onPress: resetInputHandler }]
      );
      return;
    }

    setConfirmed(true);
    setSelectedNumber(chosenNumber);
    Keyboard.dismiss();
  };

  const resetInputHandler = () => {
    setEnteredValue('');
    setConfirmed(false);
  };

  const numberInputHandler = inputText => {
    setEnteredValue(inputText.replace(/[^0-9]/g, ''));
  };

  if (confirmed) {
    const foundTickets = displayedTickets.filter(t => t.numberOfVotes >= selectedNumber);
    setDisplayedTickets(foundTickets);
    setConfirmed(false);
  }

  return (
    <View>
      <SearchBarUpvotes
        numberInputHandler={numberInputHandler}
        confirmInputHandler={confirmInputHandler}
        enteredValue={enteredValue}
      />
      <FlatList
        removeClippedSubviews={false}
        data={displayedTickets}
        renderItem={({ item }) => (
          <TicketItem ticket={item} navigation={navigation} />
        )}
        keyExtractor={item => item.id}
      />
    </View>
  );
};

const mapStateToProps = state => ({
  ticket: state.ticket
});

export default connect(mapStateToProps, {
  getTickets
})(AllTicketList);

Upvotes: 0

Views: 54

Answers (1)

Dan Lupascu
Dan Lupascu

Reputation: 307

The problem is in your second useEffect hook:

useEffect(() => {
  setDisplayedTickets(allTickets);
});

This effect, will set the displayedTickets to allTickets on every re-render.

So here's what happens:
1. When you filter the tickets, you're changing the state, and you're setting the displatedTickets to be the filtered tickets: setDisplayedTickets(foundTickets);.
2. The displayedTickets is updated, the component is re-rendered, you see the new tickets for a second, and as soon as it is re-rendered, that effect is executing again and it sets the displayedTickets to allTickets again: setDisplayedTickets(allTickets);.

So here's my advice:
1. Remove the second useEffect - that will prevent the displayedTickets to be set again to allTickets on every re-render . 2. In your flatlist change the data to displayedTickets || allTickets. In this way, when the tickets will be unfiltered - the list will display the allTickets and as soon as you filter them, the list will display the displayedTickets.

So here's how your final code should look like:

const AllTicketList = ({ navigation, ticket: { allTickets }, getTickets }) => {
  useEffect(() => {
    getTickets();
  }, []);

  const [enteredValue, setEnteredValue] = useState();
  const [selectedNumber, setSelectedNumber] = useState(false);
  const [displayedTickets, setDisplayedTickets] = useState();
  const [confirmed, setConfirmed] = useState(false);

  // Remove this effect
  //useEffect(() => {
  //  setDisplayedTickets(allTickets);
  //});

  const confirmInputHandler = () => {
    const chosenNumber = parseInt(enteredValue);
    if (isNaN(chosenNumber) || chosenNumber <= 0) {
      Alert.alert(
        'Invalid number',
        'The number of upvotes has to be greater than 0.',
        [{ text: 'Ok', style: 'destructive', onPress: resetInputHandler }]
      );
      return;
    }

    setConfirmed(true);
    setSelectedNumber(chosenNumber);
    Keyboard.dismiss();
  };

  const resetInputHandler = () => {
    setEnteredValue('');
    setConfirmed(false);
  };

  const numberInputHandler = inputText => {
    setEnteredValue(inputText.replace(/[^0-9]/g, ''));
  };

  if (confirmed) {
    const foundTickets = displayedTickets.filter(t => t.numberOfVotes >= selectedNumber);
    setDisplayedTickets(foundTickets);
    setConfirmed(false);
  }

  return (
    <View>
      <SearchBarUpvotes
        numberInputHandler={numberInputHandler}
        confirmInputHandler={confirmInputHandler}
        enteredValue={enteredValue}
      />
      <FlatList
        removeClippedSubviews={false}
        data={displayedTickets || allTickets} /* <-- displayedTickets || allTickets instead of displayedTickets */
        renderItem={({ item }) => (
          <TicketItem ticket={item} navigation={navigation} />
        )}
        keyExtractor={item => item.id}
      />
    </View>
  );
};

const mapStateToProps = state => ({
  ticket: state.ticket
});

export default connect(mapStateToProps, {
  getTickets
})(AllTicketList);

Upvotes: 1

Related Questions