2017-07-25 8 views
6

簡略化された問題。プロミス内でthis.setStateを呼び出すと、Promiseを保留中に終了する前にレンダリングされます。this.setState inside Promiseは異常な動作を引き起こします

私の問題は次のとおりである:保留中の約束が最初に閉鎖されるように

  1. this.setStateは、すぐに私はそれが非同期であることが予想
    • を返さないです。
  2. レンダリング機能内で何かが壊れると、Promise内のキャッチが呼び出されます。
    • おそらく、1)と同じ問題は、レンダリングがthis.setStateが呼び出された約束の文脈のままであるようです。

import dummydata_rankrequests from "../dummydata/rankrequests"; 
class RankRequestList extends Component { 

    constructor(props) { 
    super(props); 

    this.state = { loading: false, data: [], error: null }; 

    this.makeRankRequestCall = this.makeRankRequestCall.bind(this); 
    this.renderItem = this.renderItem.bind(this); 
    } 

    componentDidMount() { 

    // WORKS AS EXPECTED 
    // console.log('START set'); 
    // this.setState({ data: dummydata_rankrequests.data, loading: false }); 
    // console.log('END set'); 

    this.makeRankRequestCall() 
    .then(done => { 
     // NEVER HERE 
     console.log("done"); 
    });  
    } 

    makeRankRequestCall() { 
    console.log('call makeRankRequestCall'); 
    try { 
     return new Promise((resolve, reject) => { 
     resolve(dummydata_rankrequests); 
     }) 
     .then(rankrequests => { 
     console.log('START makeRankRequestCall-rankrequests', rankrequests); 
     this.setState({ data: rankrequests.data, loading: false }); 
     console.log('END _makeRankRequestCall-rankrequests'); 
     return null; 
     }) 
     .catch(error => { 
     console.log('_makeRankRequestCall-promisecatch', error); 
     this.setState({ error: RRError.getRRError(error), loading: false }); 
     }); 
    } catch (error) { 
     console.log('_makeRankRequestCall-catch', error); 
     this.setState({ error: RRError.getRRError(error), loading: false }); 
    } 
    } 

    renderItem(data) { 
    const height = 200; 
    // Force a Unknown named module error here 
    return (
     <View style={[styles.item, {height: height}]}> 
     </View> 
    ); 
    } 

    render() { 
    let data = []; 
    if (this.state.data && this.state.data.length > 0) { 
     data = this.state.data.map(rr => { 
     return Object.assign({}, rr); 
     }); 
    } 
    console.log('render-data', data); 
    return (
     <View style={styles.container}> 
     <FlatList style={styles.listContainer1} 
      data={data} 
      renderItem={this.renderItem} 
     /> 
     </View> 
    ); 
    } 
} 

とれログ示す:

  • レンダリングデータを、[]
  • START makeRankRequestCall-rankrequests
  • 、[データをレンダリングします。 ..]
  • _makeRankRequestCall-promisecatchエラー:不明な名前のモジュールに...
  • レンダリングデータを、[...]
  • 可能性のある未処理の約束

Androidのエミュレータ "反応":「16.0.0- alpha.12" 、 "反応するネイティブの": "0.46.4"、

EDIT:this.setState周り ラッピングのsetTimeoutも

0123を動作します
setTimeout(() => { 
     this.setState({ data: respData.data, loading: false }); 
    }, 1000); 

EDIT2: 並列 https://github.com/facebook/react-native/issues/15214

+0

私はあなたがしようとしている発行正確に何のトラブルの識別を持っています解決する。 'console.log(" done ");'が実行されたときに一度だけ再レンダリングしようとしていますか?もしそうなら、それを達成する一つの方法は、 'shouldComponentUpdate()'を上書きして常に 'false'を返すことです。再描画の準備ができたら' this.forceUpdate'。 https://facebook.github.io/react/docs/react-component.html#forceupdate – therobinkim

答えて

1

両方Promisethis.setState()に反応し、ネイティブのgithubの中にバグレポートを作成し、JavaScriptで非同期です。次のコードがある場合は、

console.log(a); 
networkRequest().then(result => console.log(result)); // networkRequest() is a promise 
console.log(b); 

最初にaとbが印刷され、続いてネットワーク要求の結果が表示されます。

this.setState({data: rankrequests.data},() => { 
    // Your code that needs to run after changing state 
}) 

は、それゆえあなたは、毎回this.setState()が実行されます再レンダリングリアクト:

はあなたがthis.setState()後に何かを実行したい場合は同様に、this.setState()が、そうも非同期であるが完了すると、あなたはそれを行う必要がありますプロビジョニング全体が解決される前にコンポーネントを更新する。

async componentDidMount() { 
    let rankrequests; 
    try { 
    rankrequests = await this.makeRankRequestCall() // result contains your data 
    } catch(error) { 
    console.error(error); 
    } 
    this.setState({ data: rankrequests.data, loading: false },() => { 
    // anything you need to run after setting state 
    }); 
} 

はそれが役に立てば幸い:この問題は、あなたの非同期関数としてcomponentDidMount()及び使用は約束を解決するために待つことで解決することができます。

+0

特定のコンポーネントのRN-Lifecycleメソッドほど重要なものを変更することはお勧めしません。それをASyncにすることは大きなミスに終わる可能性があります。 – GoreDefex

+0

また、状態の更新に対して何かをしている場合は、RN-Lifecycleメソッドの 'componentDidUpdate'を使うべきです – GoreDefex

1

私はあまりにもあなたがここで何をしようとしているのか理解していないので、私はそれを刺しました。

this.setState()メソッドはレンダリングをトリガーするためのものなので、レンダーする準備ができるまで呼び出すことはありません。あなたは最新の状態変数に頼りすぎて自由に使用/操作することができるようです。変数this.state.の期待される振る舞いは、レンダリング時に準備されていなければなりません。私はあなたが状態とレンダリングに結び付けられていないもう一つの可変変数を使う必要があると思います。あなたが終わったら、それだけでレンダリングすべきでしょうか?ここで

は、あなたのコードこれは見える表示するために再働いている:「../dummydata/rankrequests」から

輸入dummydata_rankrequestsを。 RankRequestList、コンポーネント{

constructor(props) { 
    super(props); 

    /* 
     Maybe here is a good place to model incoming data the first time? 
     Then you can use that data format throughout and remove the heavier modelling 
     in the render function below 

     if (this.state.data && this.state.data.length > 0) { 
      data = this.state.data.map(rr => { 
       return Object.assign({}, rr); 
      }); 
     } 
    */ 

    this.state = { 
     error: null, 
     loading: false, 
     data: (dummydata_rankrequests || []), 
    }; 

    //binding to 'this' context here is unnecessary 
    //this.makeRankRequestCall = this.makeRankRequestCall.bind(this); 
    //this.renderItem = this.renderItem.bind(this); 
} 


componentDidMount() { 
    // this.setState({ data: dummydata_rankrequests.data, loading: false }); 

    //Context of 'this' is already present in this lifecycle component 
    this.makeRankRequestCall(this.state.data).then(returnedData => { 
     //This would have no reason to be HERE before, you were not returning anything to get here 
     //Also, 
     //should try not to use double quotes "" in Javascript 


     //Now it doesn't matter WHEN we call the render because all functionality had been returned and waited for 
     this.setState({ data: returnedData, loading: false }); 

    }).catch(error => { 
     console.log('_makeRankRequestCall-promisecatch', error); 
     this.setState({ error: RRError.getRRError(error), loading: false }); 
    }); 
} 


//I am unsure why you need a bigger call here because the import statement reads a JSON obj in without ASync wait time 
//...but just incase you need it... 
async makeRankRequestCall(currentData) { 
    try { 
     return new Promise((resolve, reject) => { 
      resolve(dummydata_rankrequests); 

     }).then(rankrequests => { 
      return Promise.resolve(rankrequests); 

     }).catch(error => { 
      return Promise.reject(error); 
     }); 

    } catch (error) { 
     return Promise.reject(error); 
    } 
} 


renderItem(data) { 
    const height = 200; 

    //This is usually where you would want to use your data set 
    return (
     <View style={[styles.item, {height: height}]} /> 
    ); 

    /* 
     //Like this 
     return { 
      <View style={[styles.item, {height: height}]}> 
       { data.item.somedataTitleOrSomething } 
      </View> 
     }; 
    */ 
} 


render() { 
    let data = []; 

    //This modelling of data on every render will cause a huge amount of heaviness and is not scalable 
    //Ideally things are already modelled here and you are just using this.state.data 
    if (this.state.data && this.state.data.length > 0) { 
     data = this.state.data.map(rr => { 
      return Object.assign({}, rr); 
     }); 
    } 
    console.log('render-data', data); 

    return (
     <View style={styles.container}> 
      <FlatList 
       data={data} 
       style={styles.listContainer1} 
       renderItem={this.renderItem.bind(this)} /> 
      { /* Much more appropriate place to bind 'this' context than above */ } 
     </View> 
    ); 
} 

}

1

setStateが実際に非同期で延び

クラス。

async makeRankRequestCall() { 
    console.log('call makeRankRequestCall'); 
    try { 
    const rankrequests = await new Promise((resolve, reject) => { 
     resolve(dummydata_rankrequests); 
    }); 

    console.log('START makeRankRequestCall-rankrequests', rankrequests); 
    this.setState({ data: rankrequests.data, loading: false }); 
    console.log('END _makeRankRequestCall-rankrequests'); 
    } catch(error) { 
    console.log('_makeRankRequestCall-catch', error); 
    this.setState({ error: RRError.getRRError(error), loading: false }); 
    } 
} 

第二に、renderItemのエラーをキャッチ約束は完全に罰金です:私はmakeRankRequestCallは次のようにする必要がありますね。 JavaScriptでは、catchブロックはコード内のどこにでもスローされたエラーをキャッチします。 specsによると:あなたはrenderItemが失敗することが予想される場合

The throw statement throws a user-defined exception. Execution of the current function will stop (the statements after throw won't be executed), and control will be passed to the first catch block in the call stack. If no catch block exists among caller functions, the program will terminate.

だからそれを修正するために、あなたは、次の手順を実行できます。

renderItem(data) { 
    const height = 200; 
    let item = 'some_default_item'; 
    try { 
    // Force a Unknown named module error here 
    item = styles.item 
    } catch(err) { 
    console.log(err); 
    } 
    return (
    <View style={[item, {height: height}]}> 
    </View> 
); 
} 
関連する問題