React Native重新渲染组件时出现意外行为

时间:2019-07-12 04:32:02

标签: firebase react-native firebase-realtime-database react-native-firebase

我在我的本机应用程序中使用react-native-card-stack-swiper库。我正在使用Firebase实时数据库作为后端。

预期行为:

this.state.peopleList是{personA,personB} 此列表来自Firebase实时数据库people / peopleList。

这是Home.js

import React, {Component} from 'react';
import {FlatList, View, Text, ActivityIndicator, Alert, StyleSheet, TouchableOpacity, Dimensions} from 'react-native';
import { addToAccepted, getHiddenPosts} from "../lib/firebaseUtils";
import firebase from 'react-native-firebase';
import { Button, ListItem, Card, Icon as IconElements } from 'react-native-elements';
import ActionSheet from 'react-native-actionsheet'
import Icon from 'react-native-vector-icons/FontAwesome';
import MaterialComIcon from 'react-native-vector-icons/MaterialCommunityIcons';
import * as _ from 'lodash';
import CardStack, { Card as SwipableCard } from 'react-native-card-stack-swiper';
import OfflineNotice from './OfflineNotice';

let uid;

class HomeScreen extends Component {
    constructor(props) {
        super(props);
        this.state = {
            peopleList: [],
            hiddenPosts: [],
            fetching: false,
        };
        this.getPeopleList = this.getPeopleList.bind(this);
    }

    componentDidMount(){
        this._isMounted = true;
        this.setState({fetching:true});
        let user = firebase.auth().currentUser;
        if (user != null) {
          uid = user.uid;
        } else {
          this.props.navigation.navigate('Login')
        }

        getHiddenPosts(uid).then(hiddenPosts => {
          this.setState({hiddenPosts});
        })
    }

    componentWillUnmount()
    {
        this._isMounted = false;
    }

    /*
    * get all the task requests that this user can perform
    * */
    getPeopleList = () => {
        let networkId = this.state.networkId;
        let livePostsRef = firebase.database().ref('people/peopleList')
        livePostsRef.on('child_added', (snapshot) => {

          let request  = snapshot.val()
          // Check if it is not already decided upon by this user
          if(!_.includes(this.state.hiddenPosts, request.id))
          {
            this.setState({peopleList:[request].concat(this.state.peopleList) , fetching: false});
          }
          if(this.state.fetching) this.setState({fetching:false});
        })
        if(this._isMounted) this.setState({fetching:false});

        livePostsRef.on('child_removed', (snapshot) => {
          this.setState({peopleList: this.state.peopleList.filter(item => item.id !== snapshot.key)});
        })
    }

    // The user has decided on this card and hence add this card to the user's hidden tasks list so that the app won't show it again next time it launches
    decideOnPost = (id) =>
    {
        this.setState({peopleList: this.state.peopleList.filter(item => item.id !== id)});
        if(uid) appendHiddenPosts(uid, id);
    }

    acceptPerson = (item) =>
    {
        addToAccepted(item).then(res => {
            appendHiddenPosts(uid, id).then(finRes => {
                this.setState({peopleList: this.state.peopleList.filter(item => item.id !== id)});
            }
        }

    }


    swipableRender() {
      const {peopleList} = this.state;

      console.log('swipableRender: peopleList is ', peopleList)

      return peopleList.map((item) => {
        const {name, photo, bio, id} = item;
        console.log('swipableRender return: item.name is ', item.name)

        return (
          <SwipableCard key={id} onSwipedLeft={() => this.decideOnPost(id)} onSwipedRight={() => this.acceptPerson(item)}>
          <View>
          {console.log('swipableRender return return: customTitle is ', customTitle)}


            <Card image={{uri: bgImage}} featuredTitle={customTitle} featuredTitleStyle={adourStyle.listItemText} >


                <Text style={adourStyle.cardText}>{details}</Text>




              </Card>
              </View>
            </SwipableCard>
        )
      })

  }

    render() {
        const {fetching, peopleList} = this.state
        console.log('*** RENDERING *** peopleList: ', peopleList)
        return (
            <View style={styles.mainContainer}>
            <CardStack
                renderNoMoreCards={() => <View style={{marginTop: 50}}>
                                                  {fetching && <ActivityIndicator color={BRAND_COLOR_ONE} size={'large'}/>}
                                                  {!fetching &&
                                                    <View style={styles.cardOverText}>
                                                    <Text style={{marginBottom: 8}}>Check back later</Text>
                                                    </View>

                                                  }
                                                  </View>}
                disableBottomSwipe={true}
                disableTopSwipe={true}
                ref={swiper => {
                  this.swiper = swiper
                }}
              >
              {this.swipableRender()}
              </CardStack>
            </View>
        )
    }


}

export default HomeScreen;

卡叠最初按预期呈现。换句话说,启动该应用程序时,我看到一叠带有personA卡和personB卡的卡。这些卡可以按预期刷卡。

如果我刷出所有卡:personA和personB,则会按预期留给我文本“请稍后再检查”。

但是,如果我在屏幕上并且同时将新的personC对象添加到firebase实时数据库people / peopleList中,我希望firebase.database()。ref()。on侦听器将检测到数据库,在状态peopleList上执行setState,然后重新呈现该组件。结果,我希望看到personC出现在屏幕上。

但是实际上 Firebase侦听器按预期检测到数据库中的更改 然后我们做一个setState来更新this.state.peopleList的值 然后React按预期重新渲染组件(已使用console.log进行了验证) *但是我在屏幕上看到了personA,这很奇怪*

此外,如果我仍然在屏幕上,并且将另一个对象添加到firebase数据库中:personD,也会发生相同的情况。我没有看到personD卡,而是看到了personB卡。

作为用户,我已经向左滑动了personA和personB,并留下了一个空白屏幕。当我在屏幕上添加新对象时,没有看到新对象,而是再次看到了旧对象。

如果我完全关闭应用程序并重新启动,则会发生正确的行为:我看到personC和personD。直到我在personC和personD上向左轻扫,然后问题再次出现,除非我重新启动该应用程序。

我可以肯定问题是存在于此Home.js文件或库react-native-card-stack-swiper中。该库的开发人员不再支持该库,因此,如果该库存在任何问题,我将必须对其进行修复。

反应本机卡堆栈刷卡器

这里是react-native-card-stack-swiper(您也可以在其GitHub页面上找到源代码:https://github.com/lhandel/react-native-card-stack-swiper

这是Card.js(以SwipableCard的形式导入到我的心中)

import React, { Component } from 'react';
import PropTypes from 'prop-types'
import {
  View,
} from 'react-native';

const Card = ({ style, children }) => (
  <View style={style} >
    {children}
  </View>);

Card.propTypes = {
  children: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired,
  style: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]),
  onSwipedLeft: PropTypes.func,
  onSwipedRight:PropTypes.func,
  onSwipedTop: PropTypes.func,
  onSwipedBottom: PropTypes.func,
  onSwiped: PropTypes.func,
}
Card.defaultProps = {
  style:{},
  onSwiped: () => {},
  onSwipedLeft: () => {},
  onSwipedRight: () => {},
  onSwipedTop: () => {},
  onSwipedBottom: () => {},
}

export default Card;

这是CardStack.js

import React, { Component } from 'react';
import PropTypes from 'prop-types'
import {
  StyleSheet,
  View,
  Animated,
  PanResponder,
  Dimensions,
  Text,
  Platform
} from 'react-native';

const { height, width } = Dimensions.get('window');

export default class CardStack extends Component {


  static distance(x, y) {
    const a = Math.abs(x);
    const b = Math.abs(y);
    const c = Math.sqrt((a * a) + (b * b));
    return c;
  }

  constructor(props) {
    super(props);
    this.state ={
      drag: new Animated.ValueXY({x: 0, y: 0}),
      dragDistance: new Animated.Value(0),
      sindex: 0, // index to the next card to be renderd mod card.length
      cardA: null,
      cardB: null,
      topCard: 'cardA',
      cards: [],
      touchStart: 0,
    };
    this.distance = this.constructor.distance;
  }


  componentWillMount() {
    this._panResponder = PanResponder.create({
      onStartShouldSetPanResponder: (evt, gestureState) => false,
      onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
      onMoveShouldSetPanResponder: (evt, gestureState) =>  {
        const isVerticalSwipe = Math.sqrt(
          Math.pow(gestureState.dx, 2) < Math.pow(gestureState.dy, 2)
        )
        if (!this.props.verticalSwipe && isVerticalSwipe) {
          return false
        }
        return Math.sqrt(Math.pow(gestureState.dx, 2) + Math.pow(gestureState.dy, 2)) > 10
      }, //(parseInt(gestureState.dx) !== 0 && parseInt(gestureState.dy) !== 0),
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
        const isVerticalSwipe = Math.sqrt(
          Math.pow(gestureState.dx, 2) < Math.pow(gestureState.dy, 2)
        )
        if (!this.props.verticalSwipe && isVerticalSwipe) {
          return false
        }
        return Math.sqrt(Math.pow(gestureState.dx, 2) + Math.pow(gestureState.dy, 2)) > 10
      },  //(parseInt(gestureState.dx) !== 0 && parseInt(gestureState.dy) !== 0),
      onPanResponderGrant: (evt, gestureState) => {
        this.props.onSwipeStart();
        this.setState({ touchStart: new Date().getTime() });
      },
      onPanResponderMove: (evt, gestureState) => {
        const { verticalSwipe, horizontalSwipe } = this.props;
        const { verticalThreshold, horizontalThreshold } = this.props
        const dragDistance = this.distance((horizontalSwipe) ? gestureState.dx : 0, (verticalSwipe) ? gestureState.dy : 0 );
        this.state.dragDistance.setValue(dragDistance);
        this.state.drag.setValue({x: (horizontalSwipe) ? gestureState.dx : 0, y: (verticalSwipe) ? gestureState.dy : 0});
      },
      onPanResponderTerminationRequest: (evt, gestureState) => true,
      onPanResponderRelease: (evt, gestureState) => {
        this.props.onSwipeEnd();
        const currentTime = new Date().getTime();
        const swipeDuration = currentTime-this.state.touchStart;
        const { sindex } = this.state;
        const { verticalThreshold,
                horizontalThreshold,
                disableTopSwipe,
                disableLeftSwipe,
                disableRightSwipe,
                disableBottomSwipe,
              } = this.props;


        if (((Math.abs(gestureState.dy) > verticalThreshold)  ||
            ( Math.abs(gestureState.dy) > verticalThreshold*0.8 &&
              swipeDuration < 150)
            ) && this.props.verticalSwipe)
        {

          const swipeDirection = (gestureState.dy < 0) ? height * -1 : height;
          if(swipeDirection < 0 && !disableTopSwipe)
          {

            this._nextCard('top', gestureState.dx, swipeDirection, this.props.duration);
          }
          else if (swipeDirection > 0 && !disableBottomSwipe)
          {
            this._nextCard('bottom', gestureState.dx, swipeDirection, this.props.duration);
          }
          else
          {
            this._resetCard();
          }
        }else if (((Math.abs(gestureState.dx) > horizontalThreshold) ||
                  (Math.abs(gestureState.dx) > horizontalThreshold*0.6 &&
                  swipeDuration < 150)
                ) && this.props.horizontalSwipe) {

          const swipeDirection = (gestureState.dx < 0) ? width * -1 : width;
          if (swipeDirection < 0 && !disableLeftSwipe)
          {
            this._nextCard('left', swipeDirection, gestureState.dy, this.props.duration);
          }
          else if(swipeDirection > 0 && !disableRightSwipe)
          {
            this._nextCard('right', swipeDirection, gestureState.dy, this.props.duration);
          }
          else
          {
            this._resetCard();
          }
        }
        else
        {
          this._resetCard();
        }
      },
      onPanResponderTerminate: (evt, gestureState) => {
      },
      onShouldBlockNativeResponder: (evt, gestureState) => {
        return true;
      },
    });
  }

  componentDidMount(){
    this.initDeck();
  }

  componentWillReceiveProps(nextProps){
    if (nextProps.children !== this.props.children) {
      this.setState({
        cards: nextProps.children,
        cardA: nextProps.children[(this.state.topCard=='cardA')? this.state.sindex-2 : this.state.sindex-1],
        cardB: nextProps.children[(this.state.topCard=='cardB')? this.state.sindex-2 : this.state.sindex-1]
      });
    }
  }

  initDeck() {
    // check if we only have 1 child
    if(typeof this.props.children !== 'undefined' && !Array.isArray(this.props.children)){
      this.setState({
        cards: [this.props.children],
        cardA: this.props.children,
        cardB: null,
        sindex: 2,
      });
    }else if(Array.isArray(this.props.children)){
      this.setState({
        cards: this.props.children,
        cardA: this.props.children[0],
        cardB: this.props.children[1],
        sindex: 2,
      });
    }
  }

  _resetCard(){

    Animated.timing(
      this.state.dragDistance,
      {
        toValue: 0,
        duration: this.props.duration,
      }
    ).start();
    Animated.spring(
      this.state.drag,
      {
        toValue: {x: 0, y: 0},
        duration: this.props.duration,
      }
    ).start();

  }


  goBackFromTop(){
    this._goBack('top');
  }

  goBackFromRight(){
    this._goBack('right');
  }

  goBackFromLeft(){
    this._goBack('left');
  }

  goBackFromBottom(){
    this._goBack('bottom');
  }

  mod(n, m) {
    return ((n % m) + m) % m;
  }

  _goBack(direction){
    const {cardA, cardB, cards, sindex, topCard} = this.state;

    if((sindex-3) < 0 && !this.props.loop) return;

    const previusCardIndex = this.mod(sindex-3, cards.length)
    let update = {};
    if(topCard === 'cardA'){
      update = {
        ...update,
        cardB: cards[previusCardIndex]

      }
    }else{
      update = {
        ...update,
        cardA: cards[previusCardIndex],
      }
    }

    this.setState({
      ...update,
      topCard: (topCard === 'cardA') ? 'cardB' : 'cardA',
      sindex: sindex-1
    }, () => {

      switch (direction) {
        case 'top':
          this.state.drag.setValue({x: 0, y: -height});
          this.state.dragDistance.setValue(height);
          break;
        case 'left':
          this.state.drag.setValue({x: -width, y: 0});
          this.state.dragDistance.setValue(width);
          break;
        case 'right':
          this.state.drag.setValue({x: width, y: 0});
          this.state.dragDistance.setValue(width);
          break;
        case 'bottom':
          this.state.drag.setValue({x: 0, y: height});
          this.state.dragDistance.setValue(width);
          break;
        default:

      }

      Animated.spring(
        this.state.dragDistance,
        {
          toValue: 0,
          duration: this.props.duration,
        }
      ).start();

      Animated.spring(
        this.state.drag,
        {
          toValue: {x: 0, y: 0},
          duration: this.props.duration,
        }
      ).start();
    })
  }



  swipeTop(duration){
    this._nextCard('top', 0, -height, duration);
  }

  swipeBottom(duration){
    this._nextCard('bottom', 0, height, duration);
  }

  swipeRight(duration){
    this._nextCard('right', width, 0, duration);
  }

  swipeLeft(duration){
    this._nextCard('left', -width, 0, duration);
  }

  _nextCard(direction, x, y, duration=400){
    const { verticalSwipe, horizontalSwipe, loop } = this.props;
    const { sindex, cards, topCard } = this.state;

    // index for the next card to be renderd
    const nextCard = (loop) ? (Math.abs(sindex) % cards.length) : sindex;

    // index of the swiped card
    const index = (loop) ? this.mod(nextCard-2, cards.length) : nextCard - 2;

    if (index === cards.length-1){
      this.props.onSwipedAll();
    }


    if((sindex-2 < cards.length) || (loop) ){
      Animated.spring(
        this.state.dragDistance,
        {
          toValue: 220,
          duration,
        }
      ).start();

      Animated.timing(
        this.state.drag,
        {
          toValue: { x: (horizontalSwipe) ? x : 0, y: (verticalSwipe) ? y : 0 },
          duration,
        }
      ).start(() => {

        const newTopCard =  (topCard === 'cardA') ? 'cardB' : 'cardA';

        let update = {};
        if(newTopCard === 'cardA') {
          update = {
            ...update,
            cardB: cards[nextCard]
          };
        }
        if(newTopCard === 'cardB') {
          update = {
            ...update,
            cardA: cards[nextCard],
          };
        }
        this.state.drag.setValue({x: 0, y: 0});
        this.state.dragDistance.setValue(0);
        this.setState({
          ...update,
          topCard: newTopCard,
          sindex: nextCard+1
        });

        this.props.onSwiped(index);
        switch (direction) {
          case 'left':
            this.props.onSwipedLeft(index);
            this.state.cards[index].props.onSwipedLeft();
            break;
          case 'right':
            this.props.onSwipedRight(index);
            this.state.cards[index].props.onSwipedRight();
            break;
          case 'top':
            this.props.onSwipedTop(index);
            this.state.cards[index].props.onSwipedTop();
            break;
          case 'bottom':
            this.props.onSwipedBottom(index);
            this.state.cards[index].props.onSwipedBottom();
            break;
          default:
        }
      });

    }
  }


  /**
   * @description CardB’s click feature is trigger the CardA on the card stack. (Solved on Android)
   * @see https://facebook.github.io/react-native/docs/view#pointerevents
   */
  _setPointerEvents(topCard, topCardName) {
    return { pointerEvents: topCard === topCardName ? "auto" : "none" }
  }

  render() {

    const { secondCardZoom } = this.props;
    const { drag, dragDistance, cardA, cardB, topCard, sindex } = this.state;

    const SC = dragDistance.interpolate({
      inputRange: [0,10, 220],
      outputRange: [secondCardZoom,secondCardZoom,1],
      extrapolate: 'clamp',
    });
    const rotate = drag.x.interpolate({
      inputRange: [-320,0,320],
      outputRange: this.props.outputRotationRange,
      extrapolate: 'clamp',
    });

    return (
        <View {...this._panResponder.panHandlers} style={[{position:'relative'},this.props.style]}>

          {this.props.renderNoMoreCards()}

          <Animated.View
              {...this._setPointerEvents(topCard, 'cardB')}
              style={{
                position: 'absolute',
                zIndex: (topCard === 'cardB') ? 3 : 2,
                ...Platform.select({
                  android: {
                    elevation: (topCard === 'cardB') ? 3 : 2,
                  }
                }),
                transform: [
                  { rotate: (topCard === 'cardB') ? rotate: '0deg' },
                  {translateX: (topCard === 'cardB') ? drag.x: 0},
                  {translateY: (topCard === 'cardB') ? drag.y: 0},
                  { scale: (topCard === 'cardB') ? 1 : SC},
                ]
              }}>
              {cardB}
          </Animated.View>
          <Animated.View
              {...this._setPointerEvents(topCard, 'cardA')}
              style={{
                position: 'absolute',
                zIndex: (topCard === 'cardA') ? 3 : 2,
                ...Platform.select({
                  android: {
                    elevation: (topCard === 'cardA') ? 3 : 2,
                  }
                }),
                transform: [
                  { rotate: (topCard === 'cardA') ? rotate: '0deg' },
                  {translateX: (topCard === 'cardA') ? drag.x: 0},
                  {translateY: (topCard === 'cardA') ? drag.y: 0},
                  { scale: (topCard === 'cardA') ? 1 : SC},
                ]
              }}>
              {cardA}
          </Animated.View>

        </View>
    );
  }
}

CardStack.propTypes = {

  children: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired,

  style: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]),
  secondCardZoom: PropTypes.number,
  loop: PropTypes.bool,
  renderNoMoreCards: PropTypes.func,
  onSwipeStart: PropTypes.func,
  onSwipeEnd: PropTypes.func,
  onSwiped: PropTypes.func,
  onSwipedLeft: PropTypes.func,
  onSwipedRight:PropTypes.func,
  onSwipedTop: PropTypes.func,
  onSwipedBottom: PropTypes.func,
  onSwiped: PropTypes.func,
  onSwipedAll: PropTypes.func,

  disableBottomSwipe: PropTypes.bool,
  disableLeftSwipe: PropTypes.bool,
  disableRightSwipe: PropTypes.bool,
  disableTopSwipe: PropTypes.bool,
  verticalSwipe: PropTypes.bool,
  verticalThreshold: PropTypes.number,

  horizontalSwipe: PropTypes.bool,
  horizontalThreshold: PropTypes.number,
  outputRotationRange: PropTypes.array,
  duration: PropTypes.number

}

CardStack.defaultProps = {

  style:{},
  secondCardZoom: 0.95,
  loop: false,
  renderNoMoreCards: () => { return (<Text>No More Cards</Text>)},
  onSwipeStart: () => null,
  onSwipeEnd: () => null,
  onSwiped: () => {},
  onSwipedLeft: () => {},
  onSwipedRight: () => {},
  onSwipedTop: () => {},
  onSwipedBottom: () => {},
  onSwipedAll: async () => {
    console.log('onSwipedAll')
  },

  disableBottomSwipe: false,
  disableLeftSwipe: false,
  disableRightSwipe: false,
  disableTopSwipe: false,
  verticalSwipe: true,
  verticalThreshold: height/4,
  horizontalSwipe: true,
  horizontalThreshold: width/2,
  outputRotationRange: ['-15deg','0deg','15deg'],
  duration: 200


}

1 个答案:

答案 0 :(得分:0)

您可以针对此问题react-native-card-stack-swiper/issues/43查看此问题-希望他们会在某个时候解决它。

问题在于,当孩子改变时,swiper不会看孩子,因此我们必须强制其识别变化,在您的示例中,您可以仅使用人员的长度或最后一条消息的ID从firebase。

<CardStack {...props} key={this.peopleList.length} > {renderCards()} </CardStack>
相关问题