When changing the parent's state from a child, why does the parent not render again?

Multi tool use
When changing the parent's state from a child, why does the parent not render again?
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.
editClassInfo
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.
textInput
this.setState()
Here is a picture reference of the problem:
https://imgur.com/a/oCHRTIu
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
:
editClassInfo
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
:
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.
ClassEdit
addClass
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.
1 Answer
1
let newClass = this.state.classes;
creates a reference to the actual classes
state, you're then mutating it.
let newClass = this.state.classes;
classes
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);
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 3 at 19:33
let newClass = this.state.classes.slice()
extraData
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
I think you problem is related to stackoverflow.com/questions/29537299/….
– anhtu
Jul 3 at 3:55