1

I have a parent class that passes a function called editClassInfo to a child class. This function, is bound to the parent, and when called, immutably changes the state of the parent. However, the parent does not render again, instead having to set the state from within the parent.

Specifically, the problem is with a modal component that has a textInput that, when the text is changed, sets the state of the parent screen. However, when the modal is closed, the screen's state does not update immediately, even though the state is changed. Instead, another this.setState() has to be called in order for the render to happen again.

Here is a picture reference of the problem: https://i.stack.imgur.com/cH6yy.jpg

Here is the code:

This is the parent component.

export default class Classes extends Component {
  static navigationOptions = {
    title: 'Classes'
  };

  constructor() {
    super();

    this.state = {
      numObjects: 3.,
      classes: [
        {
          className: "Math",
          teacherName: "Someone",
        },
        {
          className: "Science",
          teacherName: "Someone",
        },
        {
          className: "Art",
          teacherName: "Someone",
        }
      ]
    };

    this.editClassInfo = this.editClassInfo.bind(this);
  }

  editClassInfo(index, infoType, value) {
    let newClass = this.state.classes;
    switch (infoType) {
      case 'className':
        newClass[index].className = value;
        break;
      case 'teacherName':
        newClass[index].teacherName = value;
        break;
    }
    this.setState({classes: newClass});
  }

  addClass(name, name2) {
    let newClass = this.state.classes.concat({className: name, teacherName: name2});
    this.setState({classes: newClass});
  }

  loadClasses = () => {
    this.setState({
        numObjects: this.state.numObjects * 2,
    })
  }

  render(){
    const classData = this.state.classes;
    console.log(this.state.classes[0]);
    return (
      <View style={styles.container}>
        <TopBar title='Classes'/>
        <View style={styles.classHeader}>
          <Text style={styles.currentClasses}> CURRENT CLASSES </Text>
          <TouchableOpacity onPress={() => {this.addClass('World History', 'Someone')}}>
            <Image
              style = {styles.addClass}
              source={require('../resources/addClass.png')}
            />
          </TouchableOpacity>
        </View>
        <FlatList
          data = { classData }
          onEndReached = {this.loadClasses}
          keyExtractor = {(item, index) => index.toString()}
          initialNumtoRender = {3}
          renderItem = {({item}) =>
            <ClassBox
              index={this.state.classes.indexOf(item)}
              editClassInfo ={this.editClassInfo}
              className={item.className}
              teacherName={item.teacherName}
            />
          }
        />
      </View>
    );
  }
}

I pass editClassInfo onto a component called ClassBox:

export default class ClassBox extends Component {
  static propTypes = {
    className: PropTypes.string.isRequired,
    teacherName: PropTypes.string.isRequired,
    index: PropTypes.number.isRequired
  };

  constructor(props) {
    super(props);

    this.state = {
      isVisible: false,
    };

    this.modalVisible = this.modalVisible.bind(this);
  }

  modalVisible(visible) {
    this.setState({isVisible: visible});
  }

  render(){
    return(
      <View>
        <ClassEdit
          index={this.props.index}
          editClassInfo={this.props.editClassInfo}
          isVisible={this.state.isVisible}
          modalClose={this.modalVisible}
          className={this.props.className}
          teacherName={this.props.teacherName}
        />
        <TouchableOpacity onPress={() => {this.modalVisible(true)}}>
          <View style={styles.container}>
            <Image
              source={{uri: 'http://via.placeholder.com/50x50'}}
              style={styles.classImage}
            />
            <View style={styles.classInfo}>
              <Text style={styles.className}>
                {this.props.className}
              </Text>
              <Text style={styles.teacherName}>
                {this.props.teacherName}
              </Text>
            </View>
          </View>
        </TouchableOpacity>
      </View>
    )
  }
}

This component contains the Child Modal ClassEdit:

export default class ClassEdit extends Component {
  static propTypes = {
    index: PropTypes.number.isRequired,
    isVisible: PropTypes.bool.isRequired,
    className: PropTypes.string.isRequired,
    teacherName: PropTypes.string.isRequired
  }

  render() {
    return(
      <Modal
        animationType="none"
        transparent={false}
        visible={this.props.isVisible}
      >
        <View style={styles.container}>
          <View style={styles.closeTop}>
            <TouchableOpacity onPress={() => {
              this.props.modalClose(false);
            }}>
              <Image
                style={styles.closeIcon}
                source={require('../resources/close.png')}
              />
            </TouchableOpacity>
          </View>
          <View style={styles.classInfo}>
            <Image
              source={{uri: 'http://via.placeholder.com/150x150'}}
              style={styles.classImage}
            />
            <TextInput
              style={styles.className}
              placeholder='Class Name'
              value={this.props.className}
              onChangeText = {(className) => {this.props.editClassInfo(this.props.index, 'className', className)}}
            />
            <TextInput
              style={styles.teacherName}
              placeholder='Teacher Name'
              value={this.props.teacherName}
              onChangeText = {(teacherName) => {this.props.editClassInfo(this.props.index, 'teacherName', teacherName)}}
            />
          </View>
        </View>
      </Modal>
    );
  }
}

It is in this last component called ClassEdit where the parent state gets changed, but when the modal is closed, the updated state is not seen, instead having to call addClass to trigger it.

I am a bit new to react-native so my code might not be the best, and the problem might be really simple, but any help would be appreciated.

Infinity
  • 41
  • 6
  • I think you problem is related to https://stackoverflow.com/questions/29537299/react-how-do-i-update-state-item1-on-setstate-with-jsfiddle. – tuledev Jul 03 '18 at 03:55

1 Answers1

1

let newClass = this.state.classes; creates a reference to the actual classes state, you're then mutating it.

To create a new array in an immutable way you can do this :

ES6 :

let newClass = [...this.state.classes];

ES5 :

let newClass = [].concat(this.state.classes);
Dyo
  • 4,429
  • 1
  • 15
  • 30
  • Thank you. This is one solution to my problem, and probably the better one as it turns out I made a mistake when I tried to immutably update my state array. I used `let newClass = this.state.classes.slice()`, but I think it accomplishes the same thing (correct me if I'm wrong). Another solution, however, which is the one I found out myself before I saw your answer was to provide an `extraData` prop to the FlatList component which was a state value for which a function "refreshed" whenever a render was needed. In hindsight, your solution is much more concise and more syntactically correct. :D – Infinity Jul 03 '18 at 19:33