Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/variables/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript React Native setState(…):无法在现有状态转换期间更新_Javascript_Reactjs_React Native - Fatal编程技术网

Javascript React Native setState(…):无法在现有状态转换期间更新

Javascript React Native setState(…):无法在现有状态转换期间更新,javascript,reactjs,react-native,Javascript,Reactjs,React Native,我目前正在开发一个React原生应用程序,它的屏幕上有一个自定义的刷卡器组件,允许用户在一组照片中刷卡。最初,我会调用一个API来加载10张照片,当当前照片索引接近存储它们的数组末尾时,用户可以滑动以加载另外10张照片 因为我正在进行分页,所以我希望跟踪用户所在的页面。例如,如果索引在0-9之间,则用户位于第一页,第二页为10-19,以此类推 我已经能够成功地跟踪用户所在的页面,但是在状态中更新页面时,我会生成一个警告,这让我觉得有更好的方法来处理这个问题 Warning: setState(.

我目前正在开发一个React原生应用程序,它的屏幕上有一个自定义的刷卡器组件,允许用户在一组照片中刷卡。最初,我会调用一个API来加载10张照片,当当前照片索引接近存储它们的数组末尾时,用户可以滑动以加载另外10张照片

因为我正在进行分页,所以我希望跟踪用户所在的页面。例如,如果索引在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
更新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
class ImageScreen扩展组件{
建造师(道具){
超级(道具)
此.state={
第页:“”,
键:“”,
项目:[]
}
}
组件willmount(){
这是我的国家({
页面:this.props.navigation.state.params.page,
key:this.props.navigation.state.params.key,
项目:this.props.navigation.state.params.array
})
}
render(){
返回(
{this.state.items.map((项,键)=>{
返回(
)
})}
)
}
_RenderWitems(索引、项目){
让oldItems=this.state.items
让newItems=oldItems.concat(项)
this.setState({items:newItems,key:index})
}
_渲染图像(索引、总计、上下文){
如果(索引>=(总计-3)){
这个。_fetchNextPage()。然后((数据)=>{
const photos=data.photos
让items=Array.apply(null,Array(photos.length)).map((v,i)=>{
返回{id:i,photo:photos[i]}
})
返回此项。\u renderWitems(索引、项目)
})
}
}
_fetchNextPage(){
返回新承诺((解决、拒绝)=>{
const currentPage=this.state.page.current
常数下一页=当前页+1
const totalPages=this.state.page.total
如果(下一页<总页数){
const PAGE_URL='&PAGE='+nextPage
获取(集合\ URL+页面\ URL+消费者\密钥)
。然后((响应)=>{
返回response.json()
})
。然后((数据)=>{
返回解析(数据)
})
.catch((错误)=>{
返回拒绝(错误)
})
}
})
}
_onMoneTumScrollEnd(e、状态、上下文){
const photoPage=数学层(state.index/10)+1
const statePage=this.state.page.current
console.log('当前页:'+照片页)
console.log('状态页:'+statePage)
如果(照片页!==状态页){
此._renderNewPage(照片页)
}
}
_渲染页面(第页){
让newPage=this.state.page
newPage.current=第页
this.setState({page:newPage})
}
}
导出默认图像屏幕
使用应更新

如果上述方法无效,那么您也可以尝试此方法

this.forceUpdate();

不确定,但我认为问题在于:

renderPagination={this.\u renderPagination}

您忘记了绑定此事件,这将创建
循环
,因为您正在该事件中使用
this.setState
。试试这个:

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

原因:无论何时使用
this.setState
React
再次渲染整个
组件
,如果没有
绑定
任何方法,则在每次
渲染
期间都会调用该方法,除非在该函数中未使用
setState
,否则不会产生任何问题,但如果在该函数中使用
setState
,则会创建
循环
,它将再次调用
render
再次调用
setState

错误消息对我来说非常清楚:调用
this.setState
的助手函数之一必须在调用
render()
时调用。那不好。不要在render上调用的函数中调用
this.setState
。@FelixKling我不知道这可能发生在哪里-你介意再看看我更新的代码吗?你需要看看
Swiper
的实现。如果传递给它的任何函数(
renderNewItems
renderNewPage
renderPagination
)在
Swiper
render
方法中调用,则不能在这些方法中调用
setState
。道具名称以
render
开头这一事实似乎表明,您不应该在相应的方法中调用
setState
。如果您只想知道呈现的是哪个页面,我相信
Swiper
提供了一种传递回调以获得这些更改通知的方法。
Swiper
在其
render
方法中调用
this.props.renderPagination
。作为
renderPagination
prop传递的函数间接调用
setState
,这就是为什么会出现该错误,您可以使用
onMomentumScrollEnd
获得页面更改的通知。@FelixKling这就是修复方法!我将所有内容移动到一个
\onMomentumScrollEnd
方法,并且不再从
renderPagination
调用状态。感谢您的关注。您建议我使用哪种方法来调用它?对于更新状态,您可以使用这些方法。当我们遇到这些错误时,我们需要强制更新状态(如果必要的话)。他为所有其他方法定义了
绑定
,但他忘记了

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