简体   繁体   English

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

[英]React Native: Props changed, component didn't re-render

After an action, props are being changed.在一个动作之后,道具正在被改变。 componentWillUpdate also triggers but, component is not re-rendering. componentWillUpdate 也会触发,但组件不会重新渲染。

See the code :看代码:

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 :控制台日志输出:

控制台日志输出

Any solutions please?请问有什么解决办法吗?


UPDATE : Whole code have been added to the question.更新:整个代码已添加到问题中。

After evaluation submission, props changes.提交评估后,道具发生变化。 the submitted evaluation will be removed from the evaluation list, but it will be still rendered.提交的评估将从评估列表中删除,但仍会呈现。 calling the loadData() through RefreshControl (Pulldown to refresh) will re-render correctly and the evaluation will be removed.通过 RefreshControl (Pulldown to refresh) 调用 loadData() 将正确重新渲染并且评估将被删除。

Thanks in advance.提前致谢。

I ran into a similar problem, where props wouldn't trickle down to list items I rendered form a parent account.我遇到了类似的问题,道具不会细细地列出我从父帐户呈现的项目。 So I had a look at your code.所以我看了你的代码。

What currently happens for you is the following:您目前发生的情况如下:

  • You initially call this.loadData();您最初调用this.loadData(); in componentDidMountcomponentDidMount
  • As your props change, shouldComponentUpdate returns true if any of the new user details didn't exist in the old user object or if any that did in the old user object exist are missing in the new one.随着您的道具更改,如果旧用户对象中不存在任何新用户详细信息,或者新用户对象中缺少旧用户对象中存在的任何新用户详细信息,则shouldComponentUpdate将返回 true。
  • if shouldComponentUpdate returns true , that will trigger componentWillUpdate and eventually render如果shouldComponentUpdate返回true ,则将触发componentWillUpdate并最终render

componentWillUpdate() isn't defined in your code, and componentDidMount only gets called once. componentWillUpdate()未在您的代码中定义,并且componentDidMount仅被调用一次。 So your component doesn't know fetch the new data and therefore render won't have the new data to display.所以你的组件不知道获取新数据,因此render不会显示新数据。

Short answer简答

Without really knowing more about your app, I'd try adding a componentWillUpdate function that calls this.loadData which will refresh your state and trigger a re-render with forceUpdate on the last line of your loadData function.在不太了解您的应用程序的情况下,我会尝试添加一个调用this.loadDatacomponentWillUpdate函数,该函数将刷新您的状态并在您的loadData函数的最后一行使用forceUpdate触发重新渲染。

Maybe check https://facebook.github.io/react/docs/react-component.html to see which functions are triggered in the component lifecycle.也许检查https://facebook.github.io/react/docs/react-component.html以查看在组件生命周期中触发了哪些功能。

Long answer长答案

However to make your app easier to maintain and probably faster as well, you might want to consider splitting the data fetching and the rendering into different components.然而,为了使您的应用程序更易于维护并且可能更快,您可能需要考虑将数据获取和渲染拆分为不同的组件。

Since you're already using redux, all the data fetching should preferably be handled in redux.由于您已经在使用 redux,因此最好在 redux 中处理所有数据获取。 So you'd have something the following:所以你会有以下内容:

Redux Reducer Redux 减速器

  • with functions like getEvaluation() and startEvaluationSubmission() to load/save evaluations to the server...使用getEvaluation()startEvaluationSubmission()等函数将评估加载/保存到服务器...
  • providing state.evaluations提供状态state.evaluations

EvaluationsContainer.jsx评估容器.jsx

  • connecting to the state and calling the functions like getEvaluation(userID)连接到状态并调用getEvaluation(userID)等函数
  • linking state.evaluations to it's props (with redux connect) and then rendering EvaluationsList.jsx with just the evaluations it needs to render.将 state.evaluations 链接到它的 props(使用 redux connect),然后仅使用它需要渲染的评估来渲染EvaluationsList.jsx

EvaluationsList.jsx评估列表.jsx

  • gets it's own (and only that one) evaluation to display as a prop from EvaluationContainer.jsxEvaluationContainer.jsx获取它自己的(并且只有那个)评估以显示为道具

This way, if any of the data changes in the store, the container will automatically trigger an update of your container and trickle down to the display component.这样,如果存储中的任何数据发生更改,容器将自动触发容器的更新并向下传输到显示组件。 You probably won't need to override react's 'shouldComponentUpdate` function, as it's pretty good and really efficient.您可能不需要覆盖 react 的 'shouldComponentUpdate' 函数,因为它非常好且非常有效。

Abhi Aiyer wrote up some really nice articles on redux: https://medium.com/front-end-developers/how-we-redux-part-1-introduction-18a24c3b7efe#.gr289pzbi Abhi Aiyer 写了一些关于 redux 的非常好的文章: https ://medium.com/front-end-developers/how-we-redux-part-1-introduction-18a24c3b7efe#.gr289pzbi

especially part 5 could be interesting for you: https://medium.com/modern-user-interfaces/how-we-redux-part-5-components-bddd737022e1#.izwodhwwk特别是第 5 部分可能对您很有趣: https : //medium.com/modern-user-interfaces/how-we-redux-part-5-components-bddd737022e1#.izwodhwwk

Hope that helps at all... that's all I could see without playing with the code.希望这有帮助......这就是我在不玩代码的情况下所能看到的。 Let me know how you get on.让我知道你是怎么办的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM