1

I have programmed an Android app which is consists of a TextInput, a Button and a FlatList. With each name That is written in the TextInput, there will be an item in the flat list. The Item title will be the TextInput content and there will be two buttons and a text right beside the list item. A button is for increasing the number that Text displays by 1 and the other one is for decreasing. here is my code:

import React, {Component} from 'react';
import {
  View,
  Text,
  TextInput,
  FlatList,
  TouchableOpacity,

} from 'react-native';
import { ListItem, SearchBar} from 'react-native-elements';


class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      task: "",
      task_array: [],


    };
  }

  ChangeText = (some_task) => {
    this.setState({task: some_task});
  };
  pushAdd = () => {
    let contact = {
      name: this.state.task,
      counter: 0,
    };
    this.setState({task_array: [...this.state.task_array, contact]});
  };








  renderListItem = ({item}) => {
   return(
     <View style={{flexDirection: 'row'}}>
     <ListItem title={item.name} style={{width:'75%', marginLeft:20}}></ListItem>
     <View style={{flexDirection:'row'}}>
         <TouchableOpacity style={{ backgroundColor: 'blueviolet', height: 20, width: 20, borderRadius: 50}} onPress={()=> item={name:item.name, counter:item.counter + 1}}><Text>+</Text></TouchableOpacity>
         <Text>{item.counter}</Text>
  //main problem       <TouchableOpacity style={{ backgroundColor: 'blueviolet', height: 20, width: 20, borderRadius: 50 }} onPress={() => item = { name: item.name, counter: item.counter - 1 }}><Text>-</Text></TouchableOpacity>

     </View>

     </View>

   );
  };


render() {
  return(
    <View style={{ backgroundColor: 'mediumturquoise', width:'100%', height:'100%'}}>
    <FlatList 
      data = {this.state.task_array}
      renderItem = {this.renderListItem}
      keyExtractor = {(item) => item.name}>
    </FlatList>
    <View style = {{flexDirection : 'row', alignContent:'center', alignItems:'center'}}>
        <TextInput onChangeText={this.ChangeText} style={{ backgroundColor: 'burlywood', height:50, width:'75%', borderRadius:30, marginBottom:20, marginLeft:20, marginRight:20}}></TextInput>
        <TouchableOpacity style={{ backgroundColor: 'chocolate', height:40, width:50, borderRadius:10, marginBottom:20}} onPress={this.pushAdd}></TouchableOpacity>
    </View>
    </View>
  );
  }
};

export default App;

I have pointed the problematic line with the comment 'main problem'. the increase and decrease button don't do their job and when I press them in the app, the number in the text remains unchanged. What change should I perform on the onPress method of my increase and decrease button to get them to work?

Elessar
  • 93
  • 2
  • 11

2 Answers2

1

in React, you you can't manipulate data directly (like you are doing in your +/- buttons onPress methods). This will have no effect, instead, you need to change state appropriately by using setState

the fastest fix to this would be changing

// this
renderListItem = ({item}) => {
// to this
renderListItem = ({item, index}) => {

to know the index of rendered item. Then, change the onPress method of your increment/decrement buttons inside these item to utilize setState instead

// from this
onPress={()=> item={name:item.name, counter:item.counter + 1}}
// to this
onPress={() =>
          this.setState({
            task_array: this.state.task_array
              .map((item, itemIndex) =>
                itemIndex !== index
                  ? item
                  : {
                    ...item,
                    counter: item.counter + 1
                  })
          })}

What happens here is we create a new task_array from the old one using .map() method of array, leaving every element untouched except the one element that was clicked - which we define by comparing indexes. In this case we increment it's counter and assign new value to task_array by using this.setState

Max
  • 4,473
  • 1
  • 16
  • 18
1

Max’s answer is one way to realize, but the performance is not good, every time the child update, the all view is rendered again. Here are my solution:
firstly, in the parent component register an event, which to receive the child update notice.

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      task: "",
      task_array: [],
    };
  }

// register the event,
componentDidMount() {
    DeviceEventEmitter.addListener('itemUpdate', this.updateData)
  }

  componentWillUnmount () {
    DeviceEventEmitter.removeListener('ModalVisible', this.updateData)
  }
  updateData = (params) => {
      let copyTaskArray = {...this.state.task_array}
      copyTaskArray[params.index].counter = params.counter
      this.state.task_array = copyTaskArray
  }

then we should move the FlatList Item into a single Component, and let it update the value

import React,{Component} from 'react';
import { View, TouchableOpacity,DeviceEventEmitter} from 'react-native';
export default class Item extends Component{
    constructor(props) {
      super(props);
      this.state = {
        counter:this.props.item.counter
      };
    }

    plus = () => {
      let oldCounter = this.state.counter;
      let newCounter = oldCounter +1;
      this.setState({
         counter: newCounter
      })
      // notify the parent
      DeviceEventEmitter.emit("itemUpdate",{index:this.props.index,counter:this.state.counter})
    }

    reduce = () => {
       // the same with plus, only oldCounter -1
    }

    render() {
      return(
       <View key={this.props.index} style={{flexDirection: 'row'}}>
       <ListItem title={this.props.item.name} style={{width:'75%', marginLeft:20}}></ListItem>
       <View style={{flexDirection:'row'}}>
           <TouchableOpacity style={{ backgroundColor: 'blueviolet', height: 20, width: 20, borderRadius: 50}} onPress={this.plus}>
           <Text>+</Text>
           </TouchableOpacity>
           <Text>{his.props.item.counter}</Text>
     <TouchableOpacity style={{ backgroundColor: 'blueviolet', height: 20, width: 20, borderRadius: 50 }} onPress={this.reduce}>
      <Text>-</Text>
     </TouchableOpacity>
       </View>
       </View>
    ) 
  }
}

In this situation, when the item scrolls over the screen and recreate, the value still exists.
In the end, we should change the parent item renderListItem method.

renderListItem = ({item, index}) => {
    return(
     <Item index={index} item={item} ></Item>       

    );
   }

I hope this can help you.

Lenoarod
  • 3,441
  • 14
  • 25
  • This is wrong on many levels. 1. `this.state.task_array = copyTaskArray` is directly against React guidelines and will immediately cause bugs. 2. relying on some EventEmitter for ui is not React way of doing it. You don't need it. You might end up on platform that doesn't support it, e.g. Windows. 3. copying props to state is against React guidelines. 4. your upper state is useless, you wouldn't be able to render anything from it, e.g. try displaying a text next to FlatList with a sum of counters of all items in task_array and see what happens – Max Oct 26 '19 at 10:04
  • @Max, thanks, this is one way to solve the problem. According to different situations take a different solution. but it must be used carefully – Lenoarod Oct 26 '19 at 15:08