避免父组件重置子组件状态

时间:2019-02-12 19:10:20

标签: reactjs react-native

我正在我的应用程序中实现日记功能,并且我有一个主JournalComponent,其中有一个SectionList,可以渲染许多JournalEntriesComponent(又称一天)。

当您按JournalEntriesComponent时,将展开所述条目的文本以全部阅读。这由this.state.isTextExpanded的本地状态管理。

我的问题是,当我在PARENT组件中执行this.setState({})时,我的孩子的JournalEntriesComponent状态将重置,即this.state.isTextExpanded键变为false(又名默认值)。

在我的父组件中,我有一个onHandleScroll方法来隐藏某些东西,这导致了我的问题,但是我也注意到与其他事物相同的行为。

我的问题是:当我的父级组件调用this.setState({})时,如何避免重置孩子的状态?

我的代码:

儿童组件:

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

父项:

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

2 个答案:

答案 0 :(得分:2)

您的keyExtractor函数导致了此问题。您使用math.random会在每个渲染中生成一个新密钥。

每个项目都应具有唯一且稳定的密钥,否则React会卸载并重新渲染它,从而导致您重置状态。

使用keyExtractor根据条目标识提供稳定的值。

答案 1 :(得分:0)

您需要将JournalEntryComponent创建为干燥(无状态)组件,然后它才能工作。对于要从​​此JournalEntryComponent触发的任何方法,应从父级调用方法。