2017-02-09 73 views
1

我目前正在對與修光組件的自定義,允許用戶通過照片集合輕掃屏幕的陣營本機應用程序時無法更新。最初,我做了一個API調用,加載10張照片,用戶可以輕掃以在當前照片索引接近它們存儲的陣列的末尾時加載另外10張。陣營本地的setState(...):現有的狀態轉換

由於我在做分頁,我想跟蹤用戶所在的頁面。例如,如果該指數爲0-9之間,那麼用戶是第一頁,10-19爲第二頁,依此類推

我已經能夠成功地跟蹤頁面,用戶是,但我的狀態,這讓我覺得有一個更好的辦法來處理這個內更新時,它產生一個警告。

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

解決了這一問題。正如Felix在評論中指出的那樣,renderPagination方法經常重新渲染,所以我使用Swiper的onMomentumScrollEnd支柱(來自react-native-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'的助手函數。這是不好的。不要在渲染時調用的函數中調用this.setState。 –

+0

@FelixKling我很困惑,可能會發生這種情況 - 你介意再看看我更新的代碼嗎? –

+0

你需要看看Swiper的實現。如果在Swiper的render方法中調用了任何傳遞給它的函數('renderNewItems','renderNewPage','renderPagination'),那麼在這些方法中不能調用'setState'。 prop名字以'render'開始的事實似乎表明你不應該在相應的方法中調用'setState'。如果你只想知道哪個頁面被渲染,我相信'Swiper'提供了一種傳遞迴調的方式來獲得這些更改的通知。 –

回答

0

使用應該更新

shouldComponentUpdate(); 

如果上述不會工作,那麼你也可以試試這個。

this.forceUpdate(); 
+0

你在暗示我稱之爲哪種方法? –

+0

用於更新狀態,您可以使用這些方法。有一段時間,我們面臨這些錯誤,那麼我們需要強制更新狀態(如果有必要的話)。 –

0

不知道,但我認爲問題是在這一行:

renderPagination = { this._renderPagination }

你忘了bind這個事件,這是因爲使用的是this.setState,這裏面創建loop。試試這個:

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

原因:當你使用this.setStateReactrender整個component再次,如果你沒有bind那麼任何方法,將每個rendering時被調用,它不會產生任何你是不是在函數中使用setState,但如果你在使用setState然後它會創建loop問題,直到,它會再打電話render再次setState .......

+0

仍然收到相同的錯誤? –

+0

OP已經在構造函數中綁定了該方法。 –

+0

他爲所有其他方法定義了「綁定」,但他忘記了綁定這一個:'_renderPagination',請檢查。 –