【问题标题】:API integration error with redux-saga in React NativeReact Native 中与 redux-saga 的 API 集成错误
【发布时间】:2018-05-25 23:18:47
【问题描述】:

我是 React Native 的新手。
目前我有一个简单的 React Native 应用程序。我想使用带有 redux-saga 的 API 获取数据。
我已经配置了 redux-saga,但是 API 配置出了点问题(我猜)。

以下是我修改的一些文件:
ZomatoAPI.js

import apisauce from 'apisauce'

const create = (baseURL = 'https://developers.zomato.com/api/v2.1/') => {
    const api = apisauce.create({
        baseURL,
        headers:{
            'Cache-Control': 'no-cache',
            'user-key': '0f929cccab4622bf301895bc0d9dd415'
        },
        // 10 sec
        timeout: 10000
    })

    const getCategories = () => api.get('categories')

    return{
        getCategories
    }
}


ReactRestoSagas.js

import { call, put } from 'redux-saga/effects'
import { path } from 'ramda'
import ReacRestoActions from '../Redux/ReactRestoRedux'
//import ZomatoAPI from '../Services/ZomatoAPI'

//const api = ReactRestoAPI.create()

export function* getCategories(api){
    const response = yield call(api.getCategories)
    if(response.ok){
        //ok, call action from redux
        yield put(ReacRestoActions.categoriesSucceed(payload))
    }else{
        yield put(ReacRestoActions.categoriesFailed())
    }
}


index.js (Sagas)

import { takeLatest, all } from 'redux-saga/effects'
//import API from '../Services/Api'
import FixtureAPI from '../Services/FixtureApi'
import DebugConfig from '../Config/DebugConfig'
import ZomatoAPI from '../Services/ZomatoAPI'

/* ------------- Types ------------- */

import { StartupTypes } from '../Redux/StartupRedux'
import { GithubTypes } from '../Redux/GithubRedux'
import { ReactRestoTypes } from '../Redux/ReactRestoRedux'

/* ------------- Sagas ------------- */

import { startup } from './StartupSagas'
import { getUserAvatar } from './GithubSagas'
import { getCategories } from './ReactRestoSagas'

/* ------------- API ------------- */

// The API we use is only used from Sagas, so we create it here and pass along
// to the sagas which need it.
const api = DebugConfig.useFixtures ? FixtureAPI : ZomatoAPI.create()

/* ------------- Connect Types To Sagas ------------- */

export default function * root () {
  yield all([
    // some sagas only receive an action
    //takeLatest(StartupTypes.STARTUP, startup),

    // some sagas receive extra parameters in addition to an action
    //takeLatest(GithubTypes.USER_REQUEST, getUserAvatar, api),

    takeLatest(ReactRestoTypes.CATEGORIES_REQUEST, getCategories, api)
  ])
}


ReactRestoRedux.js

import { createReducer, createActions } from 'reduxsauce'
import Immutable from 'seamless-immutable'

// Categories types
const { Types, Creators } = createActions({
    categoriesRequest: null,
    categoriesSucceed: ['payload'],
    categoriesFailed: null
})

export const ReactRestoTypes = Types
export default Creators

// Initial state
export const INITIAL_STATE = Immutable({
    payload: null,
    errorMessage: null,
    fetchCategories: false
})

// Reducers
export const categoriesRequest = (state) => 
    state.merge({ fetchCategories: true })

export const categoriesSucceed = (state, action) => {
    const { payload } = action
    return state.merge({ fetchCategories: false, errorMessage: null, payload})
}

export const categoriesFailed = (state) =>
    state.merge({ fetchCategories: false, errorMessage: true})

export const reducer = createReducer(INITIAL_STATE, {
    [Types.CATEGORIES_REQUEST]: categoriesRequest,
    [Types.CATEGORIES_SUCCEED]: categoriesSucceed,
    [Types.CATEGORIES_FAILED]: categoriesFailed
})


index.js (Redux)

import React, { Component } from 'react'
import { Picker, TouchableOpacity, View, ListView, Text } from 'react-native'
import { connect } from 'react-redux'
import { Icon, Header } from 'react-native-elements'
import { Button, Container, Content, Footer, Title} from 'native-base'

// For empty lists
// import AlertMessage from '../Components/AlertMessage'

// Styles
import styles from './Styles/MainRestoStyles'

class MainRestoScreen extends React.Component {
  constructor (props) {
    super(props)

    /*
    const dataObjects = []
    {
      first: [
        {title: 'First Title', description: 'First Description'},
        {title: 'Second Title', description: 'Second Description'},
        {title: 'Third Title', description: 'Third Description'},
        {title: 'Fourth Title', description: 'Fourth Description'},
        {title: 'Fifth Title', description: 'Fifth Description'},
        {title: 'Sixth Title', description: 'Sixth Description'},
        {title: 'Seventh Title', description: 'Seventh Description'},
        {title: 'Eighth Title', description: 'Eighth Description'},
        {title: 'Ninth Title', description: 'Ninth Description'},
        {title: 'Tenth Title', description: 'Tenth Description'}
      ]
    }
    */

    const rowHasChanged = (r1, r2) => r1 !== r2
    const sectionHeaderHasChanged = (s1, s2) => s1 !== s2

    // DataSource configured
    const ds = new ListView.DataSource({rowHasChanged, sectionHeaderHasChanged})

    // Datasource is always in state
    this.state = {
      dataObjects: [],
      dataSource: ds.cloneWithRowsAndSections([])
    }

    this.stateCity = { city: "ny" }
  }

  prepareCategories(){
    //check is there any data
    if(!this.props.payload){
      //no data, call api
      this.props.getCategories()
    }else{
      this.setState({
        dataObjects: this.props.payload.dataObjects,
        dataSource: this.state.dataSource(cloneWithRowsAndSections(this.props.dataObjects))
      })
    }
  }

  checkCategories(newProps){
    if(newProps.payload){
      this.setState({
        dataObjects: newProps.payload.dataObjects,
        dataSource: this.state.dataSource.cloneWithRowsAndSections(newProps.payload.dataObjects)
      })
    }
  }

  componentWillMount(){
    this.prepareCategories()
  }

  componentWillReceiveProps(newProps){
    this.checkCategories(newProps)
  }

  renderRow (rowData, sectionID) {
    // You can condition on sectionID (key as string), for different cells
    // in different sections
    return (
      <TouchableOpacity style={styles.row}>
        <Text style={styles.boldLabel}>Section {sectionID} - {rowData.title}</Text>
        <Text style={styles.label}>{rowData.description}</Text>
      </TouchableOpacity>
    )
  }

  /* ***********************************************************
  * STEP 4
  * If your datasource is driven by Redux, you'll need to
  * reset it when new data arrives.
  * DO NOT! place `cloneWithRowsAndSections` inside of render, since render
  * is called very often, and should remain fast!  Just replace
  * state's datasource on newProps.
  *
  * e.g.
    componentWillReceiveProps (newProps) {
      if (newProps.someData) {
        this.setState(prevState => ({
          dataSource: prevState.dataSource.cloneWithRowsAndSections(newProps.someData)
        }))
      }
    }
  *************************************************************/

  // Used for friendly AlertMessage
  // returns true if the dataSource is empty
  noRowData () {
    return this.state.dataSource.getRowCount() === 0
  }

  render () {
    return (
      <Container>
        <View style={styles.toolbar}>
          <Text style={styles.toolbarButton}></Text>
          <Icon name='bowl' type='entypo' size={40} color='white'/> 
          <Picker
            style={styles.dropdown}
            selectedValue={this.state.city}
            onValueChange={(city) => this.setState({city})}>
            <Picker.Item label="New York City" value="ny" />
            <Picker.Item label="New Jersey" value="nj" />
            <Picker.Item label="Los Angeles" value="la" />
            <Picker.Item label="Oklahoma City" value="oc" />
          </Picker> 
        </View>
        <Content>        
          <ListView
            contentContainerStyle={styles.listContent}
            dataSource={this.state.dataSource}
            onLayout={this.onLayout}
            renderRow={this.renderRow}
            enableEmptySections
          />
        </Content>
        <Footer style={ styles.bars }>
          <Button transparent style={ styles.buttonsMenu }>
            <Icon name='location' type='entypo' color='white' size={30}/>
          </Button>
          <Button transparent style={ styles.buttonsMenu }>
            <Icon name='heart' type='foundation' color='white' size={30}/>
          </Button>
          <Button transparent style={ styles.buttonsMenu }>
            <Icon name='bell' type='entypo' color='white' size={30}/>
          </Button>
        </Footer>
      </Container>
    )
  }
}

//MainRestoScreen.propsTypes = {}

const mapStateToProps = (state) => {
  return {
    // ...redux state to props here
    payload: state.reactResto.payload,
    errorMessage: state.reactResto.errorMessage,
    fetchCategories: state.reactResto.fetchCategories
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    getCategories: () => dispatch(ReactRestoActions.getCategories())
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(MainRestoScreen)


MainRestoScreen.js

import React, { Component } from 'react'
import { Picker, TouchableOpacity, View, ListView, Text } from 'react-native'
import { connect } from 'react-redux'
import { Icon, Header } from 'react-native-elements'
import { Button, Container, Content, Footer, Title} from 'native-base'

// For empty lists
// import AlertMessage from '../Components/AlertMessage'

// Styles
import styles from './Styles/MainRestoStyles'

class MainRestoScreen extends React.Component {
  constructor (props) {
    super(props)

    /*
    const dataObjects = []
    {
      first: [
        {title: 'First Title', description: 'First Description'},
        {title: 'Second Title', description: 'Second Description'},
        {title: 'Third Title', description: 'Third Description'},
        {title: 'Fourth Title', description: 'Fourth Description'},
        {title: 'Fifth Title', description: 'Fifth Description'},
        {title: 'Sixth Title', description: 'Sixth Description'},
        {title: 'Seventh Title', description: 'Seventh Description'},
        {title: 'Eighth Title', description: 'Eighth Description'},
        {title: 'Ninth Title', description: 'Ninth Description'},
        {title: 'Tenth Title', description: 'Tenth Description'}
      ]
    }
    */

    const rowHasChanged = (r1, r2) => r1 !== r2
    const sectionHeaderHasChanged = (s1, s2) => s1 !== s2

    // DataSource configured
    const ds = new ListView.DataSource({rowHasChanged, sectionHeaderHasChanged})

    // Datasource is always in state
    this.state = {
      dataObjects: [],
      dataSource: ds.cloneWithRowsAndSections([])
    }

    this.stateCity = { city: "ny" }
  }

  prepareCategories(){
    //check is there any data
    if(!this.props.payload){
      //no data, call api
      this.props.getCategories()
    }else{
      this.setState({
        dataObjects: this.props.payload.dataObjects,
        dataSource: this.state.dataSource(cloneWithRowsAndSections(this.props.dataObjects))
      })
    }
  }

  checkCategories(newProps){
    if(newProps.payload){
      this.setState({
        dataObjects: newProps.payload.dataObjects,
        dataSource: this.state.dataSource.cloneWithRowsAndSections(newProps.payload.dataObjects)
      })
    }
  }

  componentWillMount(){
    this.prepareCategories()
  }

  componentWillReceiveProps(newProps){
    this.checkCategories(newProps)
  }

  renderRow (rowData, sectionID) {
    // You can condition on sectionID (key as string), for different cells
    // in different sections
    return (
      <TouchableOpacity style={styles.row}>
        <Text style={styles.boldLabel}>Section {sectionID} - {rowData.title}</Text>
        <Text style={styles.label}>{rowData.description}</Text>
      </TouchableOpacity>
    )
  }

  /* ***********************************************************
  * STEP 4
  * If your datasource is driven by Redux, you'll need to
  * reset it when new data arrives.
  * DO NOT! place `cloneWithRowsAndSections` inside of render, since render
  * is called very often, and should remain fast!  Just replace
  * state's datasource on newProps.
  *
  * e.g.
    componentWillReceiveProps (newProps) {
      if (newProps.someData) {
        this.setState(prevState => ({
          dataSource: prevState.dataSource.cloneWithRowsAndSections(newProps.someData)
        }))
      }
    }
  *************************************************************/

  // Used for friendly AlertMessage
  // returns true if the dataSource is empty
  noRowData () {
    return this.state.dataSource.getRowCount() === 0
  }

  render () {
    return (
      <Container>
        <View style={styles.toolbar}>
          <Text style={styles.toolbarButton}></Text>
          <Icon name='bowl' type='entypo' size={40} color='white'/> 
          <Picker
            style={styles.dropdown}
            selectedValue={this.state.city}
            onValueChange={(city) => this.setState({city})}>
            <Picker.Item label="New York City" value="ny" />
            <Picker.Item label="New Jersey" value="nj" />
            <Picker.Item label="Los Angeles" value="la" />
            <Picker.Item label="Oklahoma City" value="oc" />
          </Picker> 
        </View>
        <Content>        
          <ListView
            contentContainerStyle={styles.listContent}
            dataSource={this.state.dataSource}
            onLayout={this.onLayout}
            renderRow={this.renderRow}
            enableEmptySections
          />
        </Content>
        <Footer style={ styles.bars }>
          <Button transparent style={ styles.buttonsMenu }>
            <Icon name='location' type='entypo' color='white' size={30}/>
          </Button>
          <Button transparent style={ styles.buttonsMenu }>
            <Icon name='heart' type='foundation' color='white' size={30}/>
          </Button>
          <Button transparent style={ styles.buttonsMenu }>
            <Icon name='bell' type='entypo' color='white' size={30}/>
          </Button>
        </Footer>
      </Container>
    )
  }
}

//MainRestoScreen.propsTypes = {}

const mapStateToProps = (state) => {
  return {
    // ...redux state to props here
    payload: state.reactResto.payload,
    errorMessage: state.reactResto.errorMessage,
    fetchCategories: state.reactResto.fetchCategories
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    getCategories: () => dispatch(ReactRestoActions.getCategories())
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(MainRestoScreen)



这是错误:


我正在寻求帮助来解决这个问题。在这里呆了几个小时。

【问题讨论】:

    标签: android api react-native redux-saga


    【解决方案1】:

    ZomatoAPI.js文件中需要导出create函数, 在文件底部添加export default create

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-16
      • 1970-01-01
      • 1970-01-01
      • 2018-09-03
      • 1970-01-01
      • 1970-01-01
      • 2019-02-21
      • 2021-10-25
      相关资源
      最近更新 更多