javascript - React Native : Props and state changed, 但未重新渲染

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

我花了一个多星期的时间才让它发挥作用。

在下面的代码中,数据是通过 props(Redux 模式)传入并被渲染的,实际上有评估,某种形式。当您填写表格并提交时,它就会被提交, Prop 和状态也会发生变化。 (该表单将从状态、 Prop 中删除)但该表单仍在 View 中显示。在代码中调用 this.loadData(); 没有帮助。但是 PullDownToRefresh 可以工作并更新组件。 RefreshControl 还调用 this.loadData();

我做错了什么?请问有什么解决办法吗? 评估.js:

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');

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 = {
            date: moment().format('DD-MM-YYYY'),
            isRefreshing: false,
            isLoading: true,
            dsource: []
    };
        this.list = {};
  }

    componentWillMount(){
        this.loadData();
    }

  componentWillReceiveProps(nextProps){
        this.processData();
  }

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

    randomNumber(){
    return ++index;
  }

  loadData(){
        this.props.getEvaluation(this.props.users);
  }

    processData(){
        let {user, evaluation} = this.props;
        let data = evaluation[user];
        let dsource = [];
        let list = {};
        if(data){
            this.setState({
                isLoading: true
            });
            if(Object.keys(data).length > 0){
                Object.keys(data).forEach((e)=>{
                    let currentEvaluation = data[e];
                    let fields = [];
                    list[currentEvaluation.evaluationId] = {};
                    if(currentEvaluation.hasOwnProperty('evaluationField')){
                        if(currentEvaluation.evaluationField.length > 0){
                            currentEvaluation.evaluationField.forEach((f)=>{
                                fields.push({
                                    ...f,
                                    value: ''
                                });
                                list[currentEvaluation.evaluationId][f.field_name] = {
                                    value: '',
                                    required: f.required
                                };
                            });
                        }
                    } else {
                    }
              dsource.push({
                        id: currentEvaluation.evaluationId,
                        title: currentEvaluation.evaluationTitle,
                        expire: currentEvaluation.evaluationExpire,
                        image: currentEvaluation.evaluationImage,
                        fields: fields
              });
            });
            }
        }
        this.list = list;
        this.setState({
            dsource,
            isLoading: false
        });

    setTimeout(()=>{
      this.forceUpdate();
    }, 1000);
    }

    async getObjectToPost(evaluationID){
        let obj = this.list;
    return this.list[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;
  }

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

    async evaluationSubmit(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){
                submitEvaluation(user, users, evalid, stringifiedObject, ()=>{
                    console.log('Running callback');
                    Alert.alert(
                        I18n.t("evaluation_submitted_title"),
                        I18n.t("evaluation_submitted_desc")
                    );
                    setTimeout(()=>{
                  this.loadData();
                }, 1000);
                });
      } 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){
    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>
    );
  }

    render() {
        let dsource = this.state.dsource;
        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"
                            />
                        }
                    >
                        {dsource.length > 0 ?
                            <View style={styles.container}>
                                <View>
                                    <KeyboardAwareScrollView>
                                        <View>
                                            {dsource.map((data)=>{
                                                return(
                                                    <View style={styles.cardContainer} key={this.randomNumber()}>
                                                        <View style={styles.cardHeader} >
                                                            <View style={styles.headerImageContainer}>
                                                                <Image style={styles.headerImage} source={{uri: data.image}} />
                                                            </View>
                                                            <View style={{ margin: 5 }}>
                                                                <Text style={styles.cardTitle}>{data.title}</Text>
                                                            </View>
                                                        </View>
                                                        <View style={{ padding: 5 }}>
                                                            {this.renderQuestions(data.fields, data.fields.length, data.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")} {data.expire}</Text>
                                                            <TouchableOpacity onPress={() => this.evaluationSubmit(data.id)} style={styles.submitButton} >
                                                                <Text style={styles.buttonText}>{I18n.t("evaluations_submit_button")}</Text>
                                                            </TouchableOpacity>
                                                        </View>
                                                    </View>
                                                );
                                            })}
                                        </View>
                                    </KeyboardAwareScrollView>
                                </View>
                            </View>
                            :
                            <View style={styles.errorContainer}>
                                <View style={styles.error}>
                                    <Text style={styles.Errortext}>
                                        {I18n.t("evaluations_no_evaluation_available")}
                                    </Text>
                                </View>
                            </View>
                        }
                    </ScrollView>
                    :<ActivityIndicator
                            animating={true}
                            style={{ paddingTop: Platform.OS == "ios" ? (height - 114)/2 : (height - 130)/2 }}
                            color={'#00a2dd'}
                            size={'small'}
                        />}
          </View>
        </View>
    );
    }
}

EvaluationPage.js

import {bindActionCreators} from "redux";
import {connect} from "react-redux";
import Evaluation from "./components/Evaluation";
import {Actions as routes} from "react-native-router-flux";
import * as evaluationActions from "./evaluation.actions";

function mapStateToProps(state) {
	return {
		user: state.auth.user,
		users: state.auth.users,
		evaluation: state.evaluation.evaluation,
		openProfile: routes.profilePage
	}
}

function dispatchToProps(dispatch) {
	return bindActionCreators({
		getEvaluation: evaluationActions.getEvaluation,
		submitEvaluation: evaluationActions.submitEvaluation
	}, dispatch);
}

export default connect(mapStateToProps, dispatchToProps)(Evaluation);

Evaluation.actions.js:

export const GET_EVALUATION = 'GET_EVALUATION';
export const GET_EVALUATION_FAILED = 'GET_EVALUATION_FAILED';
export const SUBMIT_EVALUATION = 'SUBMIT_EVALUATION';
export const SUBMIT_EVALUATION_FAILED = 'SUBMIT_EVALUATION_FAILED';

import Functions from '../common/Functions';

export const getEvaluation = (users) => {
  return dispatch => {
    Functions.getEvaluationsAPI(users)
    .then((data)=>{
      const {evaluationsList} = data;
      return dispatch(evaluationSuccess(evaluationsList));
    })
    .catch((e)=>{
      return dispatch(evaluationFailed(e));
    });
  };
}

export const submitEvaluation = (user, users, evaluationId, evaluationData, callback) => {
  return dispatch => {
    Functions.submitEvaluation(user, users, evaluationId, evaluationData)
    .then(()=>{
      console.log('Submit finished, getting evaluations');
      Functions.getEvaluationsAPI(users)
      .then((data)=>{
        const {evaluationsList} = data;
        console.log('Got evals list', evaluationsList);
        return dispatch(evaluationSubmissionSuccess(evaluationsList));
      })
      .catch((e)=>{
        return dispatch(evaluationSubmissionFailed(e));
      });
    })
    .catch((e)=>{
      return dispatch(evaluationSubmissionFailed(e));
    });
  };
}

const evaluationSuccess = (evaluationsList) => {
  return {
    type: GET_EVALUATION,
    payload: {
      evaluation: evaluationsList
    }
  }
};

const evaluationFailed = (e) => {
  return {
    type: GET_EVALUATION_FAILED,
    payload: {
      error: e
    }
  }
};

const evaluationSubmissionSuccess = (evaluationsList) => {
  console.log('Submission success returning to reducer');
  return {
    type: SUBMIT_EVALUATION,
    payload: {
      evaluation: evaluationsList
    }
  };
};

const evaluationSubmissionFailed = (e) => {
  return {
    type: SUBMIT_EVALUATION_FAILED,
    payload: {
      error: e
    }
  };
};

Evaluation.reducer.js:

import * as types from "./evaluation.actions";

export const INITIAL_STATE = {
  evaluation: {}
};

export default function evaluation(state = INITIAL_STATE, action){
  const {evaluation} = state;
  switch(action.type){
    case types.GET_EVALUATION:
    return Object.assign({}, state, {
      evaluation: action.payload.evaluation
		});
    case types.GET_EVALUATION_FAILED:
    return {
      ...state,
      evaluation
    };
    case types.SUBMIT_EVALUATION:
    let newObject = Object.assign({}, state, {
      evaluation: action.payload.evaluation
		});
    return newObject;
    case types.SUBMIT_EVALUATION_FAILED:
    return {
      ...state,
      evaluation
    };
    default:
    return state;
  }
}

商店.js

import { persistStore, autoRehydrate } from "redux-persist";
import { combineReducers } from "redux";
import {REHYDRATE} from 'redux-persist/constants';
import { applyMiddleware, createStore, compose } from "redux";
import createActionBuffer from 'redux-action-buffer';
import { AsyncStorage } from "react-native";
import thunk from "redux-thunk";
import createLogger from "redux-logger";
import rootReducer from "./rootReducer";

const logger = createLogger();

const enhancer = compose(
  autoRehydrate(),
  applyMiddleware(
    thunk,
    logger,
    createActionBuffer(REHYDRATE)
  )
);

const store = createStore(
  rootReducer,
  {},
  enhancer
);

persistStore(store, {storage: AsyncStorage});

export default store;

rootReducer.js

import { combineReducers } from "redux";
import auth from "./auth/auth.reducer";
import storage from "./storage/storage.reducer";
import courses from "./courses/courses.reducer";
import goals from "./goals/goals.reducer";
import evaluation from "./evaluation/evaluation.reducer";

const rootReducer = combineReducers({
    auth,
    storage,
    courses,
    goals,
    evaluation
});

export default rootReducer;

根.js:

import React from "react";
import { View } from "react-native";
import { Provider } from "react-redux";
import store from "./store";
import Routes from "./Routes";

const Root = () => (
    <Provider store={store}>
        <View style={{flex: 1}}>
            <Routes />
        </View>
    </Provider>
);

export default Root;

最佳答案

刚发现问题。在 componentWillReceiveProps 上,我调用了 this.processData() ,而 processData() 正在从 this.props 获取数据code> 时,我应该将 nextProps 传递给 processData() 并从 nextProps 读取数据。

componentWillReceiveProps(nextProps){
this.processData(nextProps);
}

processData(nextProps){
/// process data based on nextProps
}

关于javascript - React Native : Props and state changed, 但未重新渲染,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42062799/

相关文章:

javascript - 以 AngularJS 的方式使用 toastr

javascript - connected-react-router - 你不应该在 <Router> 之外使用 <Route>

javascript - Expo IntentLauncher无法打开Application_Details_Settings

react-native - react 导航动态 header 不起作用?

javascript - React-router v3 中具有嵌套路由的索引路由

react-native-google-places-autocomplete 不起作用

javascript - 不同 block 中的事件部分开始和结束标记

javascript - 并行调用可变数量的函数?

javascript - 将拖放文件发送到输入类型文件元素

reactjs - 在React-Hooks UseRef中我无法获取新的状态值