React Native:道具改变了,组件没有重新渲染

时间:2017-02-02 13:33:03

标签: javascript reactjs react-native redux react-redux

行动结束后,道具正在改变。 componentWillUpdate也会触发,但组件不会重新渲染。

参见代码:

import React, {Component, PropTypes} from "react";
import {
    ActivityIndicator,
  Text,
  View,
  Image,
  NetInfo,
  Alert,
  TouchableOpacity,
  ScrollView,
  TextInput,
  Dimensions,
    RefreshControl,
  Platform
} from 'react-native';
import { styles, moment, GoogleAnalytics, KeyboardAwareScrollView, DeviceInfo, Loader, Accordion, I18n, CustomNavBar, DatePicker, FMPicker, CustomStarRating, Icon, CustomPicker, CustomQuestion, CustomDatePicker } from "../../common/components";
let { width, height } = Dimensions.get('window');
GoogleAnalytics.setTrackerId('UA-86421142-1');
GoogleAnalytics.trackScreenView('Evaluation Page');
GoogleAnalytics.setDispatchInterval(5);
var index = 0;

export default class Evaluation extends Component {
    static propTypes = {
        user: PropTypes.string.isRequired,
    users: PropTypes.object.isRequired,
        evaluation: PropTypes.object.isRequired,
    getEvaluation: PropTypes.func.isRequired,
        submitEvaluation: PropTypes.func.isRequired
    };

    constructor(props) {
    super(props);
    this.state = {
            isLoading: true,
            evaluationList: '',
            totalEval: 0,
            date: moment().format('DD-MM-YYYY'),
            isRefreshing: false
    };
        this.list = {};
  }

  componentDidMount(){
    this.loadData();
        NetInfo.isConnected.fetch().then(isConnected => {
      this.setState({
        isConnected: isConnected
      });
    });
    NetInfo.isConnected.addEventListener(
      'change',
      isConnected => {
        this.setState({
          isConnected: isConnected
        });
      }
    );
  }

    shouldComponentUpdate(nextProps, nextState){
        let shouldUpdate = false;
        const oldValue = this.props.evaluation[this.props.user];
        const newValue = nextProps.evaluation[nextProps.user];
        Object.keys(newValue).forEach((index)=>{
            if(!oldValue.hasOwnProperty(index)){
                shouldUpdate = true;
            }
        });
        Object.keys(oldValue).forEach((index)=>{
            if(!newValue.hasOwnProperty(index)){
                shouldUpdate = true;
            }
        });
        console.log('should component update?', shouldUpdate);
        return shouldUpdate;
    }

    randomNumber(){
    index++;
    return index;
  }

  async loadData(cb = ()=>{}){
        await this.props.getEvaluation(this.props.users);
    let {user, users, evaluation, getEvaluation} = this.props;
    let data = evaluation[user];
    let dsource = [];
        let list = {};
    Object.keys(data).forEach((e)=>{
            let currentEvaluation = data[e];
            let fields = [];
            list[currentEvaluation.evaluationId] = {};
            currentEvaluation.evaluationField.forEach((f)=>{
                fields.push({
                    ...f,
                    value: ''
                });
                list[currentEvaluation.evaluationId][f.field_name] = {
                    value: '',
                    required: f.required
                };
            });
      dsource.push({
                id: currentEvaluation.evaluationId,
                title: currentEvaluation.evaluationTitle,
                expire: currentEvaluation.evaluationExpire,
                image: currentEvaluation.evaluationImage,
                count: currentEvaluation.evaluationField.length,
                fields: fields
      });
    });
        this.list = list;
    this.setState({
            evaluationList: dsource,
            isLoading: false,
            totalEval: dsource.length,
    });
        this.forceUpdate();
        cb();
  }

    async getObjectToPost(evaluationID){
        let obj = this.list;
    return obj[evaluationID];
  }

    async changeValue(a,b,c,type){
        let list = this.list;
    if(type == 'date' || type == 'picker'){
      list[a][b].value = c;
    } else {
      let oldValue = this.getValue(a,b);
      if(oldValue != c){
                list[a][b].value = c;
            }
    }
        this.list = list;
  }

  async getValue(id, name){
        let list = this.list;
        return list[id][name].value;
  }

    async startEvaluationSubmission(user, users, id, data, cb){
        await cb(user, users, id, data, ()=>{
            Alert.alert(
                I18n.t("evaluation_submitted_title"),
                I18n.t("evaluation_submitted_desc")
            );
        });
    }

    async submitEvaluation(evalid){
        const {user, users, evaluation, getEvaluation, submitEvaluation} = this.props;
    let allRequiredEnetered = true;
    let objToPost = {};
    let answers = await this.getObjectToPost(evalid);
    for(let key in answers){
      objToPost[key]=answers[key].value;
      if(answers[key].required == true && answers[key].value == ''){
        allRequiredEnetered = false;
      }
    }
    if(allRequiredEnetered){

      objToPost = {
        result: objToPost
      };
      let stringifiedObject = JSON.stringify(objToPost);

      if(this.state.isConnected){
                this.startEvaluationSubmission(user, users, evalid, stringifiedObject, submitEvaluation);
      } else {
                //// Save evaluation to submit later.
        Alert.alert(
          I18n.t("offline_mode_title"),
          I18n.t("evaluation_offline")
        );
      }

    } else {
      Alert.alert(
        I18n.t("invalid_input_title"),
        I18n.t("please_fill_in")
      );
    }
  }

    renderQuestions(EvaluationFields,TotalEvaluationsCount,EvaluationID){ // evald.fields, evald.count, evald.id
    let tmdata = [];
    for(let n=0; n < TotalEvaluationsCount; n++){
      if(n > 0){
        tmdata.push(
        <View key={this.randomNumber()} style={styles.separator}></View>
        );
      }
      tmdata.push(
        <Text key={this.randomNumber()} style={styles.questionTitle} >{EvaluationFields[n].label}{EvaluationFields[n].required > 0 ? ' *' : ''}{'\n'}</Text>
      );
      switch (EvaluationFields[n].type) {
        case 'date':
        let currentValue = this.getValue(EvaluationID, EvaluationFields[n].field_name);
        let dateToShow = this.props.date;
        if(currentValue.length != undefined && currentValue.length != ''){
          dateToShow = currentValue;
        }
        tmdata.push(
          <View style={styles.datepicker} key={this.randomNumber()}>
          <CustomDatePicker
            mode="date"
            placeholder={I18n.t("select_date")}
            format="DD-MM-YYYY"
            minDate="01-01-2000"
            maxDate="01-01-2099"
            showIcon={false}
            confirmBtnText={I18n.t("confirm_button")}
            cancelBtnText={I18n.t("login_page_scan_cancel")}
            onDateChange={(date) => {this.changeValue(EvaluationID, EvaluationFields[n].field_name, date, 'date');}}
            required={EvaluationFields[n].required > 0 ? true : false}
          />
          </View>
        );
        break;
        case 'text':
        tmdata.push(
          <TextInput
            key={this.randomNumber()}
            style={[styles.textinput, Platform.OS == "android" ? { borderWidth: 0, height: 35 } : {}]}
            onChangeText={(text) => {this.changeValue(EvaluationID, EvaluationFields[n].field_name, text, 'text');}}
            maxLength = {Number(EvaluationFields[n].max_length)}
            autoCorrect={false}
            autoCapitalize={'none'}
            clearButtonMode={'always'}
            placeholder={I18n.t("evaluations_comment_field")}
          />
        );
        break;
        case 'rate':
        tmdata.push(
          <View key={this.randomNumber()} style={styles.starrating}>
            <CustomStarRating
              maxStars={Number(EvaluationFields[n].stars)}
              rating={Number(this.getValue(EvaluationID, EvaluationFields[n].field_name))}
              selectedStar={(rating) => {this.changeValue(EvaluationID, EvaluationFields[n].field_name, rating, 'rating');}}
              starSize={(width / (Number(EvaluationFields[n].stars))) > ( width / 10) ? ( width / 10) : (width / (Number(EvaluationFields[n].stars)))}
              required={EvaluationFields[n].required > 0 ? true : false}
            />
          </View>
        );
        break;
      }
      if(EvaluationFields[n].type == 'list'){
        if(EvaluationFields[n].widget == 'note'){
          tmdata.push(
            <View key={this.randomNumber()}>
              <CustomQuestion
                  evaluationId={EvaluationID}
                  fieldName={EvaluationFields[n].field_name}
                  allowedValues={EvaluationFields[n].allowed_values}
                  noteColors={EvaluationFields[n].note_colors}
                  onChange={(value)=>{ this.changeValue(EvaluationID, EvaluationFields[n].field_name, value, 'custom') }}
                  required={EvaluationFields[n].required > 0 ? true : false}
              />
            </View>
          );
        } else {
          let allowedValues = EvaluationFields[n].allowed_values;
          let Options=[];
          let LabelsForOptions=[];

          for(let r=0; r < allowedValues.length; r++){
            Options.push(allowedValues[r][0]);
            LabelsForOptions.push(allowedValues[r][1]);
          }
          tmdata.push(
            <View style={Platform.OS == "ios" ? styles.picker : styles.pickerSimple} key={this.randomNumber()}>
              <CustomPicker
                options={Options}
                labels={LabelsForOptions}
                onSubmit={(option) => {this.changeValue(EvaluationID, EvaluationFields[n].field_name, option, 'picker');}}
                confirmBtnText={I18n.t("confirm_button")}
                cancelBtnText={I18n.t("login_page_scan_cancel")}
                text={I18n.t("please_select_answer")}
                required={EvaluationFields[n].required > 0 ? true : false}
              />
            </View>
          );
        }
      }
    }
    return(
      <View key={this.randomNumber()}>{tmdata}</View>
    );
  }

    renderRow() {
        if(!this.state.isLoading){
    let eval_length = this.state.totalEval;
    let content = [];
    let evaluationList = this.state.evaluationList;

    for(let x=0; x < eval_length; x++){
      let evald = evaluationList[x];

      content.push(
        <View style={[styles.cardContainer, (x+1) == eval_length ? { marginBottom: 6 } : {}]} key={this.randomNumber()}>
          <View style={styles.cardHeader} >
            <View style={styles.headerImageContainer}>
              <Image style={styles.headerImage} source={{uri: evald.image}} />
            </View>
            <View style={{ margin: 5 }}>
              <Text style={styles.cardTitle}>{evald.title}</Text>
            </View>
          </View>
          <View style={{ padding: 5 }}>
            {this.renderQuestions(evald.fields, evald.count, evald.id)}
          </View>
          <View style={{ padding: 5 }}>
            <View style={styles.separator}></View>
            <Text style={styles.footerText}>{I18n.t("evaluations_mandatory")}{'\n'}{I18n.t("evaluations_desc_expire")} {evald.expire}</Text>
            <TouchableOpacity onPress={() => this.submitEvaluation(evald.id)} style={styles.submitButton} >
              <Text style={styles.buttonText}>{I18n.t("evaluations_submit_button")}</Text>
            </TouchableOpacity>
          </View>
        </View>
      );
    }
      return(
        <View>
          <KeyboardAwareScrollView>
            <View key={this.randomNumber()}>
              {content}
            </View>
          </KeyboardAwareScrollView>
        </View>
      );
    }
  }

    renderData(){
        if(this.state.totalEval > 0){
      return(
                <View style={styles.container} key={this.randomNumber()}>
            {this.renderRow()}
        </View>
      );
    } else {
      return(
        <View style={styles.errorContainer}>
          <View style={styles.error}>
            <Text style={styles.Errortext}>
              {I18n.t("evaluations_no_evaluation_available")}
            </Text>
          </View>
        </View>
      );
    }
  }

    render() {
        const {user, users, evaluation, getEvaluation} = this.props;
        return (
        <View style={styles.container}>
          <View style={{ width: width, height: Platform.OS == "ios" ? 64 : 54}}>
            <CustomNavBar
              width={width}
              height={Platform.OS == "ios" ? 64 : 54}
              title={I18n.t("evaluation_page_nav_title")}
              titleSize={18}
              buttonSize={15}
              background={"#00a2dd"}
              color={"#FFF"}
              rightIcon={"ios-person-outline"}
              rightIconSize={30}
              rightAction={()=> { this.props.openProfile(); }}
            />
          </View>
          <View style={{ height: Platform.OS == "ios" ? height - 114 : height - 130 }}>
            {!this.state.isLoading ?
              <ScrollView
              refreshControl={
                <RefreshControl
                  refreshing={this.state.isRefreshing}
                  onRefresh={this.loadData.bind(this)}
                  tintColor="#00a2dd"
                  title=""
                  titleColor="#00a2dd"
                  colors={['#00a2dd', '#00a2dd', '#00a2dd']}
                  progressBackgroundColor="#FFFFFF"
                />
              }
            >
              {this.renderData()}
            </ScrollView>
            :<ActivityIndicator
                        animating={true}
                        style={{ paddingTop: Platform.OS == "ios" ? (height - 114)/2 : (height - 130)/2 }}
                        color={'#00a2dd'}
                        size={'small'}
                    />}
          </View>
        </View>
    );
    }
}

控制台日志输出:

Console log output

有任何解决方案吗?

更新: 整个代码已添加到问题中。

评估提交后,道具发生变化。提交的评估将从评估列表中删除,但仍将呈现。通过RefreshControl调用loadData()(刷新下拉)将正确重新渲染,评估将被删除。

提前致谢。

1 个答案:

答案 0 :(得分:1)

我遇到了类似的问题,其中道具不会流下来列出我从父帐户呈现的项目。所以我看了你的代码。

目前您遇到的情况如下:

  • 您最初在this.loadData();
  • 中致电componentDidMount
  • 当您的道具更改时,如果旧用户对象中不存在任何新用户详细信息,或者旧用户对象中存在的任何内容在新用户对象中存在,则shouldComponentUpdate将返回true。< / LI>
  • 如果shouldComponentUpdate返回true,则会触发componentWillUpdate,最终会render
您的代码中未定义

componentWillUpdate()componentDidMount只会被调用一次。因此,您的组件无法获取新数据,因此render将无法显示新数据。

简短回答

如果不真正了解您的应用,我会尝试添加一个调用componentWillUpdate的{​​{1}}函数,该函数会刷新您的状态并触发最后一次使用this.loadData的重新渲染forceUpdate函数的行。

也许检查https://facebook.github.io/react/docs/react-component.html以查看在组件生命周期中触发了哪些功能。

答案很长

但是,为了使您的应用程序更易于维护并且可能更快,您可能需要考虑将数据提取和渲染拆分为不同的组件。

由于您已经在使用redux,因此最好在redux中处理所有数据提取。所以你有以下内容:

Redux Reducer

  • 使用loadDatagetEvaluation()等函数将评估加载/保存到服务器...
  • 提供startEvaluationSubmission()

EvaluationsContainer.jsx

  • 连接到州并调用state.evaluations
  • 等功能
  • 将state.evaluations链接到它的props(使用redux connect),然后仅使用需要渲染的评估来渲染getEvaluation(userID)

EvaluationsList.jsx

  • EvaluationsList.jsx
  • 获取它自己(并且只有那个)评估显示为道具

这样,如果商店中的任何数据发生更改,容器将自动触发容器更新并向下显示到显示组件。您可能不需要覆盖react的'shouldComponentUpdate`函数,因为它非常好并且非常有效。

Abhi Aiyer在redux上写了一些非常好的文章:https://medium.com/front-end-developers/how-we-redux-part-1-introduction-18a24c3b7efe#.gr289pzbi

尤其是第5部分可能对您有意义: https://medium.com/modern-user-interfaces/how-we-redux-part-5-components-bddd737022e1#.izwodhwwk

希望有所帮助......这就是我在不玩代码的情况下所能看到的。让我知道你是怎么过的。