首页 > 解决方案 > 未定义不是对象(评估'props.contacts.reduce') - React Native

问题描述

我正在学习 React Native 并尝试运行我学到的这个应用程序,但对我来说,会抛出这样的错误。未定义不是对象(评估'props.contacts.reduce')

编辑:我正在添加此应用程序中使用的所有其他代码。我找不到错误。请帮忙。我只是在学习它是如何工作的,我不是创建这个的人。这是错误指向的程序:

SectionListContacts.js

import React from 'react'
import {SectionList, Text} from 'react-native'
import PropTypes from 'prop-types'

import Row from './Row'

const renderSectionHeader = ({section}) => <Text>{section.title}</Text>

const SectionListContacts = props => {
  const contactsByLetter = props.contacts.reduce((obj, contact) => {
    const firstLetter = contact.name[0].toUpperCase()
    return {
      ...obj,
      [firstLetter]: [...(obj[firstLetter] || []), contact],
    }
  }, {})

  const sections = Object.keys(contactsByLetter)
    .sort()
    .map(letter => ({
      data: contactsByLetter[letter],
      title: letter,
    }))

  return (
    <SectionList
      keyExtractor={item => item.phone}
      sections={sections}
      renderItem={({item}) => <Row {...item} onSelectContact={props.onSelectContact} />}
      renderSectionHeader={renderSectionHeader}
    />
  )
}

SectionListContacts.propTypes = {
  contacts: PropTypes.array,
}

export default SectionListContacts

应用程序.js

import React from 'react'
import {
  createStackNavigator,
  createSwitchNavigator,
  createBottomTabNavigator,
} from 'react-navigation'
import Ionicons from 'react-native-vector-icons/Ionicons'
import {Provider} from 'react-redux'

import AddContactScreen from './screens/AddContactScreen'
import SettingsScreen from './screens/SettingsScreen'
import ContactListScreen from './screens/ContactListScreen'
import ContactDetailsScreen from './screens/ContactDetailsScreen'
import LoginScreen from './screens/LoginScreen'
import {fetchUsers} from './api'
import contacts from './contacts'
import store from './redux/store'

const MainStack = createStackNavigator(
  {
    ContactList: ContactListScreen,
    ContactDetails: ContactDetailsScreen,
    AddContact: AddContactScreen,
  },
  {
    initialRouteName: 'ContactList',
    navigationOptions: {
      headerTintColor: '#a41034',
      headerStyle: {
        backgroundColor: '#fff',
      },
    },
  }
)

MainStack.navigationOptions = {
  tabBarIcon: ({focused, tintColor}) => (
    <Ionicons name={`ios-contacts${focused ? '' : '-outline'}`} size={25} color={tintColor} />
  ),
}

const MainTabs = createBottomTabNavigator(
  {
    Contacts: MainStack,
    Settings: SettingsScreen,
  },
  {
    tabBarOptions: {
      activeTintColor: '#a41034',
    },
  }
)

const AppNavigator = createSwitchNavigator({
  Login: LoginScreen,
  Main: MainTabs,
})

export default class App extends React.Component {
  state = {
    contacts,
  }

  /*
  componentDidMount() {
    this.getUsers()
  }

  getUsers = async () => {
    const results = await fetchUsers()
    this.setState({contacts: results})
  }
  */

  addContact = newContact => {
    this.setState(prevState => ({
      contacts: [...prevState.contacts, newContact],
    }))
  }

  render() {
    return (
      <Provider store={store}>
        <MainTabs />
      </Provider>
    )
  }
}

api.js

const processContact = contact => ({
  name: `${contact.name.first} ${contact.name.last}`,
  phone: contact.phone,
})

export const fetchUsers = async () => {
  const response = await fetch('https://randomuser.me/api/?results=50&nat=us')
  const {results} = await response.json()
  return results.map(processContact)
}

export const login = async (username, password) => {
  const response = await fetch('http://localhost:8000', {
    method: 'POST',
    headers: {'content-type': 'application/json'},
    body: JSON.stringify({username, password}),
  })

  if (response.ok) {
    return true
  }

  const errMessage = await response.text()
  throw new Error(errMessage)
}

AddContactForm.js

import React from 'react'
import {Button, KeyboardAvoidingView, StyleSheet, TextInput, View} from 'react-native'

export default class AddContactForm extends React.Component {
  state = {
    name: '',
    phone: '',
    isFormValid: false,
  }

  componentDidUpdate(prevProps, prevState) {
    if (this.state.name !== prevState.name || this.state.phone !== prevState.phone) {
      this.validateForm()
    }
  }

  getHandler = key => val => {
    this.setState({[key]: val})
  }

  handleNameChange = this.getHandler('name') // val => { this.setState({name: val}) }
  handlePhoneChange = this.getHandler('phone')

  /*
  handleNameChange = name => {
    this.setState({name})
  }
  */

  handlePhoneChange = phone => {
    if (+phone >= 0 && phone.length <= 10) {
      this.setState({phone})
    }
  }

  validateForm = () => {
    console.log(this.state)
    const names = this.state.name.split(' ')
    if (
      +this.state.phone >= 0 &&
      this.state.phone.length === 10 &&
      names.length >= 2 &&
      names[0] &&
      names[1]
    ) {
      this.setState({isFormValid: true})
    } else {
      this.setState({isFormValid: false})
    }
  }

  validateForm2 = () => {
    if (+this.state.phone >= 0 && this.state.phone.length === 10 && this.state.name.length >= 3) {
      return true
    }
    return false
  }

  handleSubmit = () => {
    this.props.onSubmit(this.state)
  }

  render() {
    return (
      <KeyboardAvoidingView behavior="padding" style={styles.container}>
        <TextInput
          style={styles.input}
          value={this.state.name}
          onChangeText={this.getHandler('name')}
          placeholder="Name"
        />
        <TextInput
          keyboardType="numeric"
          style={styles.input}
          value={this.state.phone}
          onChangeText={this.getHandler('phone')}
          placeholder="Phone"
        />
        <Button title="Submit" onPress={this.handleSubmit} disabled={!this.state.isFormValid} />
      </KeyboardAvoidingView>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  input: {
    borderWidth: 1,
    borderColor: 'black',
    minWidth: 100,
    marginTop: 20,
    marginHorizontal: 20,
    paddingHorizontal: 10,
    paddingVertical: 5,
    borderRadius: 3,
  },
})

联系人.js

const NUM_CONTACTS = 3

const firstNames = [
  'Emma',
  'Noah',
  'Olivia',
  'Liam',
  'Ava',
  'William',
  'Sophia',
  'Mason',
  'Isabella',
  'James',
  'Mia',
  'Benjamin',
  'Charlotte',
  'Jacob',
  'Abigail',
  'Michael',
  'Emily',
  'Elijah',
  'Harper',
  'Ethan',
  'Amelia',
  'Alexander',
  'Evelyn',
  'Oliver',
  'Elizabeth',
  'Daniel',
  'Sofia',
  'Lucas',
  'Madison',
  'Matthew',
  'Avery',
  'Aiden',
  'Ella',
  'Jackson',
  'Scarlett',
  'Logan',
  'Grace',
  'David',
  'Chloe',
  'Joseph',
  'Victoria',
  'Samuel',
  'Riley',
  'Henry',
  'Aria',
  'Owen',
  'Lily',
  'Sebastian',
  'Aubrey',
  'Gabriel',
  'Zoey',
  'Carter',
  'Penelope',
  'Jayden',
  'Lillian',
  'John',
  'Addison',
  'Luke',
  'Layla',
  'Anthony',
  'Natalie',
  'Isaac',
  'Camila',
  'Dylan',
  'Hannah',
  'Wyatt',
  'Brooklyn',
  'Andrew',
  'Zoe',
  'Joshua',
  'Nora',
  'Christopher',
  'Leah',
  'Grayson',
  'Savannah',
  'Jack',
  'Audrey',
  'Julian',
  'Claire',
  'Ryan',
  'Eleanor',
  'Jaxon',
  'Skylar',
  'Levi',
  'Ellie',
  'Nathan',
  'Samantha',
  'Caleb',
  'Stella',
  'Hunter',
  'Paisley',
  'Christian',
  'Violet',
  'Isaiah',
  'Mila',
  'Thomas',
  'Allison',
  'Aaron',
  'Alexa',
  'Lincoln',
]

const lastNames = [
  'Smith',
  'Jones',
  'Brown',
  'Johnson',
  'Williams',
  'Miller',
  'Taylor',
  'Wilson',
  'Davis',
  'White',
  'Clark',
  'Hall',
  'Thomas',
  'Thompson',
  'Moore',
  'Hill',
  'Walker',
  'Anderson',
  'Wright',
  'Martin',
  'Wood',
  'Allen',
  'Robinson',
  'Lewis',
  'Scott',
  'Young',
  'Jackson',
  'Adams',
  'Tryniski',
  'Green',
  'Evans',
  'King',
  'Baker',
  'John',
  'Harris',
  'Roberts',
  'Campbell',
  'James',
  'Stewart',
  'Lee',
  'County',
  'Turner',
  'Parker',
  'Cook',
  'Mc',
  'Edwards',
  'Morris',
  'Mitchell',
  'Bell',
  'Ward',
  'Watson',
  'Morgan',
  'Davies',
  'Cooper',
  'Phillips',
  'Rogers',
  'Gray',
  'Hughes',
  'Harrison',
  'Carter',
  'Murphy',
]

// generate a random number between min and max
const rand = (max, min = 0) => Math.floor(Math.random() * (max - min + 1)) + min

// generate a name
const generateName = () =>
  `${firstNames[rand(firstNames.length - 1)]} ${lastNames[rand(lastNames.length - 1)]}`

// generate a phone number
const generatePhoneNumber = () => `${rand(999, 100)}-${rand(999, 100)}-${rand(9999, 1000)}`

// create a person
const createContact = () => ({
  name: generateName(),
  phone: generatePhoneNumber(),
})

// compare two contacts for alphabetizing
export const compareNames = (contact1, contact2) => contact1.name > contact2.name

// add keys to based on index
const addKeys = (val, key) => ({key, ...val})

// create an array of length NUM_CONTACTS and add keys
export default Array.from({length: NUM_CONTACTS}, createContact).map(addKeys)

FlatListContacts.js

import React from 'react'
import {FlatList} from 'react-native'
import PropTypes from 'prop-types'

import Row from './Row'

const renderItem = ({item}) => <Row {...item} />

const FlatListContacts = props => <FlatList renderItem={renderItem} data={props.contacts} />

FlatListContacts.propTypes = {
  contacts: PropTypes.array,
}

export default FlatListContacts

行.js

import React from 'react'
import {TouchableOpacity, StyleSheet, Text, View} from 'react-native'
import PropTypes from 'prop-types'

const styles = StyleSheet.create({
  row: {padding: 20},
})

const Row = props => (
  <TouchableOpacity style={styles.row} onPress={() => props.onSelectContact(props)}>
    <Text>{props.name}</Text>
    <Text>{props.phone}</Text>
  </TouchableOpacity>
)

Row.propTypes = {
  name: PropTypes.string,
  phone: PropTypes.string,
}

export default Row

ScrollViewContacts.js

import React from 'react'
import {ScrollView} from 'react-native'
import PropTypes from 'prop-types'

import Row from './Row'

const ScrollViewContacts = props => (
  <ScrollView>{props.contacts.map(contact => <Row {...contact} />)}</ScrollView>
)

ScrollViewContacts.propTypes = {
  contacts: PropTypes.array,
}

export default ScrollViewContacts

(目录:屏幕)

AddContactScreen.js

import React from 'react'
import AddContactForm from '../AddContactForm'
import {connect} from 'react-redux'

import {addContact} from '../redux/actions'

class AddContactScreen extends React.Component {
  static navigationOptions = {
    headerTitle: 'New Contact',
  }

  handleSubmit = formState => {
    this.props.addContact({name: formState.name, phone: formState.phone})
    this.props.navigation.navigate('ContactList')
  }

  render() {
    return <AddContactForm onSubmit={this.handleSubmit} />
  }
}

export default connect(null, {addContact: addContact})(AddContactScreen)

ContactDetailsS​​creen.js

import React from 'react'
import {Button, Text, View} from 'react-native'

export default class ContactDetailsScreen extends React.Component {
  static navigationOptions = ({navigation}) => ({
    headerTitle: navigation.getParam('name'),
  })

  render() {
    return (
      <View>
        <Text>{this.props.navigation.getParam('phone')}</Text>
        <Button title="Go to random contact" onPress={this.goToRandomContact} />
      </View>
    )
  }

  goToRandomContact = () => {
    const {contacts} = this.props.screenProps
    const phone = this.props.navigation.getParam('phone')
    let randomContact
    while (!randomContact) {
      const randomIndex = Math.floor(Math.random() * contacts.length)
      if (contacts[randomIndex].phone !== phone) {
        randomContact = contacts[randomIndex]
      }
    }

    // this.props.navigation.navigate('ContactDetails', {
    //   ...randomContact,
    // });
    this.props.navigation.push('ContactDetails', {
      ...randomContact,
    })
  }
}

ContactListScreen.js

import React from 'react'
import {Button, View, StyleSheet} from 'react-native'
import {connect} from 'react-redux'

import SectionListContacts from '../SectionListContacts'

class ContactListScreen extends React.Component {
  static navigationOptions = ({navigation}) => ({
    headerTitle: 'Contacts',
    headerRight: (
      <Button title="Add" onPress={() => navigation.navigate('AddContact')} color="#a41034" />
    ),
  })

  state = {
    showContacts: true,
  }

  toggleContacts = () => {
    this.setState(prevState => ({showContacts: !prevState.showContacts}))
  }

  handleSelectContact = contact => {
    this.props.navigation.push('ContactDetails', contact)
  }

  render() {
    return (
      <View style={styles.container}>
        <Button title="toggle contacts" onPress={this.toggleContacts} />
        {this.state.showContacts && (
          <SectionListContacts
            contacts={this.props.contacts}
            onSelectContact={this.handleSelectContact}
          />
        )}
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
})

const mapStateToProps = state => ({
  contacts: state.contacts,
})

export default connect(mapStateToProps)(ContactListScreen)

登录屏幕.js

import React from 'react'
import {Button, View, StyleSheet, Text, TextInput} from 'react-native'

import {login} from '../api'

export default class LoginScreen extends React.Component {
  state = {
    username: '',
    password: '',
  }

  _login = async () => {
    try {
      const success = await login(this.state.username, this.state.password)
      this.props.navigation.navigate('Main')
    } catch (err) {
      const errMessage = err.message
      this.setState({err: errMessage})
    }
  }

  handleUsernameUpdate = username => {
    this.setState({username})
  }

  handlePasswordUpdate = password => {
    this.setState({password})
  }

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.error}>{this.state.err}</Text>
        <TextInput
          placeholder="username"
          value={this.state.username}
          onChangeText={this.handleUsernameUpdate}
          autoCapitalize="none"
        />
        <TextInput
          placeholder="password"
          value={this.state.password}
          onChangeText={this.handlePasswordUpdate}
          secureTextEntry
        />
        <Button title="Press to Log In" onPress={this._login} />
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    justifyContent: 'center',
    flex: 1,
  },
  text: {
    textAlign: 'center',
  },
  error: {
    textAlign: 'center',
    color: 'red',
  },
})

设置屏幕.js

import React from 'react'
import {Button, View, StyleSheet, Text} from 'react-native'

import Ionicons from 'react-native-vector-icons/Ionicons'

export default class SettingsScreen extends React.Component {
  static navigationOptions = {
    tabBarIcon: ({focused, tintColor}) => (
      <Ionicons name={`ios-options${focused ? '' : '-outline'}`} size={25} color={tintColor} />
    ),
  }
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.text}>Settings coming soon.</Text>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    justifyContent: 'center',
    flex: 1,
  },
  text: {
    textAlign: 'center',
  },
})

(目录结束)

(目录:Redux)

Actions.js

// action types
export const UPDATE_USER = 'UPDATE_USER'
export const UPDATE_CONTACT = 'UPDATE_CONTACT'

// action creators
export const updateUser = update => ({
  type: UPDATE_USER,
  payload: update,
})

export const addContact = newContact => ({
  type: UPDATE_CONTACT,
  payload: newContact,
})

store.js

import {createStore} from 'redux'

import {addContact} from './actions'
import reducer from './reducer'

const store = createStore(reducer)

/*
store.dispatch(updateUser({foo: 'foo'}))
store.dispatch(updateUser({bar: 'bar'}))
store.dispatch(updateUser({foo: 'baz'}))
*/

store.dispatch(addContact({name: 'jordan h', phone: '1234567890'}))
store.dispatch(addContact({name: 'jordan h', phone: '1234567890'}))
store.dispatch(addContact({name: 'david m', phone: '5050505050'}))

console.log(store.getState())

export default store

减速器.js

import {combineReducers} from 'redux'

import {UPDATE_USER, UPDATE_CONTACT} from './actions'

const merge = (prev, next) => Object.assign({}, prev, next)

const contactReducer = (state = [], action) => {
  if (action.type === UPDATE_CONTACT) return [...state, action.payload]
  return state
}

const userReducer = (state = {}, action) => {
  switch (action.type) {
    case UPDATE_USER:
      return merge(state, action.payload)
    case UPDATE_CONTACT:
      return merge(state, {prevContact: action.payload})
    default:
      return state
  }
}

const reducer = combineReducers({
  user: userReducer,
  contacts: contactReducer,
})

export default reducer

(目录结束)

标签: react-nativereact-native-android

解决方案


您的组件代码不是问题:正在渲染的组件SectionListContacts没有传递 property contacts。这就是为什么在运行时props.contactsundefined并且应用程序抱怨您不能在其上使用方法 reduce 的原因。


推荐阅读