2017-02-09 25 views
1

現在のところ、ユーザーが写真のコレクションをスワイプできるカスタムスワイパーコンポーネントの画面を持つReact Nativeアプリで作業しています。最初に私は10枚の写真を読み込むAPI呼び出しを行い、現在の写真インデックスが格納されている配列の終わりに近づくとスワイプして別の10を読み込みます。React Native setState(...):既存の状態遷移中に更新できません

ページ作成を行っているので、ユーザーがどのページを表示しているかを記録します。たとえば、インデックスが0〜9の場合、ユーザーは最初のページ、10〜19は2番目のページなどです。

ユーザーがいるページを正常に追跡できましたしかし、状態の中でそれを更新するときに私は警告を生成しているので、これを処理するより良い方法があると私は思う。ここで

Warning: setState(...): Cannot update during an existing state transition 
(such as within `render` or another component's constructor). Render 
methods should be a pure function of props and state; constructor side 
effects are an anti-pattern, but can be moved to `componentWillMount`. 

は、画面の私の実装です:

'use strict' 

import React, { Component } from 'react' 
import { Text, View, Image, Dimensions, Platform } from 'react-native' 
import { StackNavigator } from 'react-navigation' 
import Swiper from 'react-native-swiper' 
import styles from './styles/ImageScreenStyle' 

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

class ImageScreen extends Component { 
    constructor(props) { 
    super(props) 
    this.state = { 
     page: this.props.navigation.state.params.page, 
     key: this.props.navigation.state.params.key, 
     items: this.props.navigation.state.params.array, 
    } 
    this._fetchNextPage = this._fetchNextPage.bind(this) 
    this._renderNewItems = this._renderNewItems.bind(this) 
    this._renderNewPage = this._renderNewPage.bind(this) 
    } 

    // Update the parent state to push in new items 
    _renderNewItems(index, items) { 
    let oldItems = this.state.items 
    let newItems = oldItems.concat(items) 
    this.setState({ items: newItems, key: index }) 
    } 

    // This generates a warning but still works? 
    _renderNewPage(page) { 
    let newPage = this.state.page 
    newPage.current = page 
    this.setState({ page: newPage }) 
    } 

    render() { 
    return (
     <Swiper 
     showsButtons 
     loop = { false } 
     index = { this.state.key } 
     renderPagination = { this._renderPagination } 
     renderNewItems = { this._renderNewItems } 
     renderNewPage = { this._renderNewPage } 
     fetchNextPage = { this._fetchNextPage } 
     page = { this.state.page }> 
     { this.state.items.map((item, key) => { 
      return (
      <View key = { key } style = { styles.slide }> 
       <Image 
       style = {{ width, height }} 
       resizeMode = 'contain' 
       source = {{ uri: item.photo.images[1].url }} 
       /> 
      </View> 
     ) 
     })} 
     </Swiper> 
    ) 
    } 

    _renderPagination(index, total, context) { 
    const photoPage = Math.floor(index/10) + 1 
    const currentPage = this.page.current 

    // Update the current page user is on 
    if (photoPage !== currentPage) { 
     return this.renderNewPage(photoPage) 
    } 

    // Add more photos when index is greater or equal than second last item 
    if (index >= (total - 3)) { 
     this.fetchNextPage().then((data) => { 
     // Here is where we will update the state 
     const photos = data.photos 

     let items = Array.apply(null, Array(photos.length)).map((v, i) => { 
      return { id: i, photo: photos[i] } 
     }) 

     // Pass in the index because we want to retain our location 
     return this.renderNewItems(index, items) 
     }) 
    } 
    } 

    _fetchNextPage() { 
    return new Promise((resolve, reject) => { 
     const currentPage = this.state.page.current 
     const nextPage = currentPage + 1 
     const totalPages = this.state.page.total 

     if (nextPage < totalPages) { 
     const PAGE_URL = '&page=' + nextPage 

     fetch(COLLECTION_URL + PAGE_URL + CONSUMER_KEY) 
     .then((response) => { 
      return response.json() 
     }) 
     .then((data) => { 
      return resolve(data) 
     }) 
     .catch((error) => { 
      return reject(error) 
     }) 
     } 
    }) 
    } 
} 

export default ImageScreen 

改ページは一つの関数内で処理され、そして私は新しい写真やページの状態を処理するために_renderNewItemsと_renderNewPageメソッドを使用していますインデックス。

更新

私は、提供の回答を反映するように私のコードを変更しましたが、警告を抑制するためになって任意の運を持っていないです。私は束縛を考え出し、componentWillMount()メソッドに変更すると助けになります。問題を修正しました

class ImageScreen extends Component { 
    constructor(props) { 
    super(props) 
    this.state = { 
     page: '', 
     key: '', 
     items: [] 
    } 
    this._fetchNextPage = this._fetchNextPage.bind(this) 
    this._renderNewItems = this._renderNewItems.bind(this) 
    this._renderNewPage = this._renderNewPage.bind(this) 
    } 

    componentWillMount() { 
    this.setState({ 
     page: this.props.navigation.state.params.page, 
     key: this.props.navigation.state.params.key, 
     items: this.props.navigation.state.params.array 
    }) 
    } 

    render() { 
    return (
     <Swiper 
     showsButtons 
     loop = { false } 
     index = { this.state.key } 
     renderPagination = { this._renderPagination.bind(this) } 
     renderNewItems = { this._renderNewItems.bind(this) } 
     renderNewPage = { this._renderNewPage.bind(this) } 
     fetchNextPage = { this._fetchNextPage.bind(this) }> 
     { this.state.items.map((item, key) => { 
      return (
      <View key = { key } style = { styles.slide }> 
       <Image 
       style = {{ width, height }} 
       resizeMode = 'contain' 
       source = {{ uri: item.photo.images[1].url }} 
       /> 
      </View> 
     ) 
     })} 
     </Swiper> 
    ) 
    } 

    _renderPagination(index, total, context) { 
    const photoPage = Math.floor(index/10) + 1 
    const statePage = this.state.page.current 

    if (photoPage !== statePage) { 
     return this._renderNewPage(photoPage) 
    } 


    if (index >= (total - 3)) { 
     this._fetchNextPage().then((data) => { 
     const photos = data.photos 

     let items = Array.apply(null, Array(photos.length)).map((v, i) => { 
      return { id: i, photo: photos[i] } 
     }) 

     return this._renderNewItems(index, items) 
     }) 
    } 
    } 

    _renderNewItems(index, items) { 
    let oldItems = this.state.items 
    let newItems = oldItems.concat(items) 
    this.setState({ items: newItems, key: index }) 
    } 

    // TO-DO: Fix the warning this generates 
    _renderNewPage(page) { 
    let newPage = this.state.page 
    newPage.current = page 
    this.setState({ page: newPage }) 
    } 

    _fetchNextPage() { 
    return new Promise((resolve, reject) => { 
     const currentPage = this.state.page.current 
     const nextPage = currentPage + 1 
     const totalPages = this.state.page.total 

     if (nextPage < totalPages) { 
     const PAGE_URL = '&page=' + nextPage 

     fetch(COLLECTION_URL + PAGE_URL + CONSUMER_KEY) 
     .then((response) => { 
      return response.json() 
     }) 
     .then((data) => { 
      return resolve(data) 
     }) 
     .catch((error) => { 
      return reject(error) 
     }) 
     } 
    }) 
    } 
} 

export default ImageScreen 

アップデート2

:私は現在立つのはここです。フェリックスがコメントで指摘したように、renderPaginationメソッドが頻繁に再レンダリングされていたので、スワイパーのonMomentumScrollEnd小道具(反応ネイティブswiperから)を使用してページ情報を更新しました。それを必要とするかもしれない人のために、ここに私のコードは次のとおりです。上記の場合

class ImageScreen extends Component { 
    constructor(props) { 
    super(props) 
    this.state = { 
     page: '', 
     key: '', 
     items: [] 
    } 
    } 

    componentWillMount() { 
    this.setState({ 
     page: this.props.navigation.state.params.page, 
     key: this.props.navigation.state.params.key, 
     items: this.props.navigation.state.params.array 
    }) 
    } 

    render() { 
    return (
     <Swiper 
     showsButtons 
     loop = { false } 
     index = { this.state.key } 
     onMomentumScrollEnd = { this._onMomentumScrollEnd.bind(this) } 
     renderPagination = { this._renderPagination.bind(this) } 
     renderNewItems = { this._renderNewItems.bind(this) } 
     fetchNextPage = { this._fetchNextPage.bind(this) }> 
     { this.state.items.map((item, key) => { 
      return (
      <View key = { key } style = { styles.slide }> 
       <Image 
       style = {{ width, height }} 
       resizeMode = 'contain' 
       source = {{ uri: item.photo.images[1].url }} 
       /> 
      </View> 
     ) 
     })} 
     </Swiper> 
    ) 
    } 

    _renderNewItems(index, items) { 
    let oldItems = this.state.items 
    let newItems = oldItems.concat(items) 
    this.setState({ items: newItems, key: index }) 
    } 

    _renderPagination(index, total, context) { 
    if (index >= (total - 3)) { 
     this._fetchNextPage().then((data) => { 
     const photos = data.photos 

     let items = Array.apply(null, Array(photos.length)).map((v, i) => { 
      return { id: i, photo: photos[i] } 
     }) 

     return this._renderNewItems(index, items) 
     }) 
    } 
    } 

    _fetchNextPage() { 
    return new Promise((resolve, reject) => { 
     const currentPage = this.state.page.current 
     const nextPage = currentPage + 1 
     const totalPages = this.state.page.total 

     if (nextPage < totalPages) { 
     const PAGE_URL = '&page=' + nextPage 

     fetch(COLLECTION_URL + PAGE_URL + CONSUMER_KEY) 
     .then((response) => { 
      return response.json() 
     }) 
     .then((data) => { 
      return resolve(data) 
     }) 
     .catch((error) => { 
      return reject(error) 
     }) 
     } 
    }) 
    } 

    _onMomentumScrollEnd(e, state, context) { 
    const photoPage = Math.floor(state.index/10) + 1 
    const statePage = this.state.page.current 
    console.log('Current page: ' + photoPage) 
    console.log('State page: ' + statePage) 


    if (photoPage !== statePage) { 
     this._renderNewPage(photoPage) 
    } 
    } 

    _renderNewPage(page) { 
    let newPage = this.state.page 
    newPage.current = page 
    this.setState({ page: newPage }) 
    } 
} 

export default ImageScreen 
+2

エラーメッセージがかなり明確と思われます私に: 'render()'が呼び出されたときに 'this.setState'を呼び出すヘルパー関数の1つを呼び出さなければなりません。それは良くありません。レンダリング時に呼び出される関数で 'this.setState'を呼び出さないでください。 –

+0

@FelixKling私は、これが起こっている可能性がある場所を困惑しています。私の更新されたコードをもう一度見てもらえますか? –

+0

あなたは 'Swiper'の実装を見る必要があります。 'renderNewItems'、' renderNewPage'、 'renderPagination'のような関数が' Swiper'の 'render'メソッドの中で呼び出された場合、それらのメソッドでは' setState'を呼び出すことはできません。プロペラ名が 'render'で始まるという事実は、対応するメソッドで' setState'を呼び出すべきでないことを示しているようです。あなたが望むのはレンダリングされるページを知ることだけだとすれば、 'Swiper'はコールバックを渡してこれらの変更を通知する手段を提供すると確信しています。 –

答えて

0

使用は

shouldComponentUpdate(); 

を更新する必要があり、あなたもこれを試すことができます動作しません。

renderPagination = { this._renderPagination }

あなたはbindにこのイベントを忘れてしまった、とあなたはこの内、this.setStateを使用しているので、これはloopを作成します

this.forceUpdate(); 
+0

私はこれをどのような方法で提案していますか? –

+0

状態を更新するには、これらのメソッドを使用できます。私たちがこれらのエラーに直面したとき、強制的に状態を更新する必要があります。 –

0

わからないが、私が考える問題は、この行です。これを試してみてください:

renderPagination = { this._renderPagination.bind(this) } 

理由を:あなたは再び、Reactrender全体componentthis.setStateを使用するたびにあなたがbindrendering中に呼び出されます、その後、任意の方法、しなかった場合、そして、それはいずれも作成されることはありません問題あなたがその機能にsetStateを使用していないが、あなたはそれでsetStateを使用している場合、それはloopを作成するまで、それが再びsetState再びrenderを呼び出します.......

+0

はまだ同じエラーが発生していますか? –

+0

OPはコンストラクタのメソッドを既にバインドしています。 –

+0

彼は他のすべてのメソッドのための 'binding'を定義しましたが、' bind''これは '_renderPagination'をチェックしてください。 –

関連する問題