Reputation: 2558
I am implementing a journal functionality into my app and I have a main JournalComponent
that has a SectionList
which renders many JournalEntriesComponent
(aka the entry of a day).
When you press on JournalEntriesComponent
you expand the text of said entry to read it all. This is managed by the local state of this.state.isTextExpanded
.
My problem is, that when I do this.setState({})
in my PARENT component, my CHILD'S JournalEntriesComponent
state gets a reset i.e. the this.state.isTextExpanded
key becomes false
(aka the default value).
In my parent component I have a onHandleScroll
method to hide something and this is causing my issue but I have also noticed the same behavior with other things.
My question is: How do I avoid the resetting of my childs state when my parent component calls this.setState({})
?
My code:
CHILD COMPONENT:
class JournalEntryComponent extends PureComponent {
constructor(props) {
super(props)
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental &&
UIManager.setLayoutAnimationEnabledExperimental(true)
}
this.state = { isTextExpanded: false }
this.onExpand = this.onExpand.bind(this)
this.onRegenerateData = this.onRegenerateData.bind(this)
this.deriveColorGradientFromRating = this.deriveColorGradientFromRating.bind(
this
)
}
onRegenerateData() {
return this.props.generateSectionListData()
}
onExpand() {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
return this.setState({ isTextExpanded: !this.state.isTextExpanded })
}
deriveColorGradientFromRating(rating) {
// decide wether to show custom moood or a default mood by looking into settings in props
const {
colorPalette,
customMoodSelected,
shouldUseDefaultColorPalettes,
} = this.props.settings.moodColorCategory
const mapRatingToArrayIndex = { [5]: 0, [4]: 1, [3]: 2, [2]: 3, [1]: 4 }
if (!shouldUseDefaultColorPalettes) {
// find the correct custom color palette and return array with colors
let relevantArrayOfGradients = this.props.settings.moodColorCategory[
customMoodSelected
]
let darkColor =
relevantArrayOfGradients[mapRatingToArrayIndex[rating]].dark
let lightColor =
relevantArrayOfGradients[mapRatingToArrayIndex[rating]].light
return [lightColor, darkColor]
}
// else return color gradients from default color palettes
return COLOR_MAP[colorPalette].colorMap[rating].colors
}
render() {
const { isTextExpanded } = this.state
const { element, isExpandedFromParent, onDisplayDayMoodPress } = this.props
console.log('isTextExpanded', isTextExpanded)
return (
<View style={styles.sectionEntryContainer}>
<View style={styles.sectionDayContainer}>
<LinearGradient
style={styles.gradientDayStyle}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
colors={this.deriveColorGradientFromRating(
element.item.data.rating
)}
>
{Platform.OS === 'ios' ? (
<View style={styles.blackLayer}>
<Text style={styles.sectionDayText} numberOfLines={1}>
{element.item.day}
</Text>
</View>
) : (
<View style={styles.blackLayerAndroid}>
<Text style={styles.sectionDayTextAndroid} numberOfLines={1}>
{element.item.day}
</Text>
</View>
)}
</LinearGradient>
</View>
<View style={styles.sectionDayDescriptionContainer}>
<TouchableWithoutFeedback onPress={this.onExpand}>
<LinearGradient
style={styles.gradientDescriptionStyle}
start={{ x: 0, y: 1 }}
end={{ x: 0, y: 0 }}
colors={['#d4d5d6', '#eef2f3']}
>
<Text
style={styles.sectionDescriptionText}
numberOfLines={
isTextExpanded || isExpandedFromParent ? null : 2
}
>
{element.item.data.optionalDescription}
</Text>
</LinearGradient>
</TouchableWithoutFeedback>
{isTextExpanded ? (
<TouchableOpacity
style={styles.gradientButtonContainer}
onPress={() =>
onDisplayDayMoodPress(element.item.day, element.item.month)
}
>
<LinearGradient
style={styles.gradientButtonStyle}
start={{ x: 0, y: 1 }}
end={{ x: 0, y: 0 }}
colors={['#d4d5d6', '#eef2f3']}
>
<Icon name={'ios-arrow-forward'} style={styles.arrowStyle} />
</LinearGradient>
</TouchableOpacity>
) : null}
</View>
</View>
)
}
}
const mapStateToProps = state => {
return {
settings: state.settings,
}
}
export default JournalEntryComponent
PARENT COMPONENT:
class JournalComponent extends PureComponent {
constructor(props) {
super(props)
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental &&
UIManager.setLayoutAnimationEnabledExperimental(true)
}
this.state = {
sectionListData: [],
expandAllEntries: false,
emotionSurveyDateToDisplay: null,
isShowEmotionSurveyVisible: false,
isCloseJournalModeButtonVisible: true,
}
this.onHandleScroll = this.onHandleScroll.bind(this)
this.renderMonthHeader = this.renderMonthHeader.bind(this)
this.renderMonthEntries = this.renderMonthEntries.bind(this)
this.onExpandAllEntries = this.onExpandAllEntries.bind(this)
this.onCloseJournalMode = this.onCloseJournalMode.bind(this)
this.onDisplayDayMoodPress = this.onDisplayDayMoodPress.bind(this)
this.generateSectionListData = this.generateSectionListData.bind(this)
}
componentWillMount() {
return this.generateSectionListData()
}
componentWillReceiveProps(nextProps) {
if (
this.props.moods[moment().format('YYYY')] !==
nextProps[moment().format('YYYY')]
) {
return this.generateSectionListData(
nextProps.moods[moment().format('YYYY')]
)
}
}
onHandleScroll(scrollEvent) {
console.log(scrollEvent.nativeEvent.contentOffset.y)
if (scrollEvent.nativeEvent.contentOffset.y > height * 2) {
if (this.state.isCloseJournalModeButtonVisible) {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
return this.setState({ isCloseJournalModeButtonVisible: false })
}
} else if (scrollEvent.nativeEvent.contentOffset.y < height * 2) {
if (!this.state.isCloseJournalModeButtonVisible) {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
return this.setState({ isCloseJournalModeButtonVisible: true })
}
}
}
onExpandAllEntries() {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
return this.setState({ expandAllEntries: !this.state.expandAllEntries })
}
onCloseJournalMode() {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
return this.props.onCloseJournalMode()
}
renderMonthHeader(el) {
return (
<View style={styles.sectionListHeaderContainer}>
<Text style={styles.sectionListHeaderText}>{el.section.title}</Text>
</View>
)
}
renderMonthEntries(el) {
return (
<JournalEntryComponent
element={el}
onDisplayDayMoodPress={this.onDisplayDayMoodPress}
isExpandedFromParent={this.state.expandAllEntries}
generateSectionListData={this.generateSectionListData}
/>
)
}
render() {
const hasNotch = DeviceInfo.hasNotch()
return (
<View style={styles.contentContainer}>
<View style={styles.contentContainer}>
<View style={styles.mainHeaderContainer}>
<View style={styles.sideHeaderButtonContainer}>
{hasNotch ? (
<Text style={styles.mainSideBarTitleText}>
{moment().format('Do / M')}
</Text>
) : null}
</View>
<View style={styles.mainHeaderTitleContainer}>
{hasNotch ? null : (
<Text style={styles.mainHeaderTitleText}>
{strings.JournalComponent.journalTitle.title}
</Text>
)}
</View>
<TouchableOpacity
style={styles.sideHeaderButtonContainer}
onPress={this.onExpandAllEntries}
>
<Icon
name={
this.state.expandAllEntries ? 'ios-contract' : 'ios-expand'
}
style={styles.expandIcon}
/>
</TouchableOpacity>
</View>
<View style={styles.contentContainer}>
{this.state.sectionListData.length === 0 ? (
<View style={styles.noDataContainer}>
<Text style={styles.noEntriesText}>
{strings.JournalComponent.noEntries.message1}
</Text>
<Text style={styles.noEntriesText}>
{strings.JournalComponent.noEntries.message2}
</Text>
</View>
) : (
<SectionList
onMomentumScrollBegin={e =>
console.log('onMomentumScrollBegin', e)
}
onScroll={this.onHandleScroll}
stickySectionHeadersEnabled={false}
renderItem={this.renderMonthEntries}
renderSectionHeader={this.renderMonthHeader}
sections={this.state.sectionListData}
keyExtractor={(item, index) =>
`index-${index}-${Math.random() / 100}`
}
/>
)}
</View>
</View>
{this.state.isCloseJournalModeButtonVisible ? (
<TouchableOpacity
style={styles.closeModalContainer}
onPress={this.onCloseJournalMode}
>
<View style={styles.closeModalIconBorder}>
<Icon name="ios-close" style={styles.closeModalIcon} />
</View>
</TouchableOpacity>
) : null}
</View>
)
}
}
export default JournalComponent
Upvotes: 1
Views: 584
Reputation: 9822
Your keyExtractor
function is causing this. Your use of math.random
is generating a new key each render.
Each item should have a unique and stable key, otherwise React will unmount it and re-render it, causing the state resetting you experience.
Use the keyExtractor
to provide a stable value, based on the entry identity.
Upvotes: 2
Reputation: 129
You need to create JournalEntryComponent as a dry(stateless) component and it will work. For any method you want to trigger from this JournalEntryComponent should call method from parent.
Upvotes: 0