react-native - 未定义不是对象(评估'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)
ContactDetailsScreen.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
(目录结束)
解决方案
您的组件代码不是问题:正在渲染的组件SectionListContacts
没有传递 property contacts
。这就是为什么在运行时props.contacts
是undefined
并且应用程序抱怨您不能在其上使用方法 reduce 的原因。
推荐阅读
- windows - 使用 Python 3 读取和写入 Windows“标签”
- c# - asp.net Core/ - 将 linq 查询转换为 Dto 时遇到问题
- node.js - NodeJS + mongoDB 又一个查询:Promises/callback
- html - Angular 8:Form-Control,名称为“StartDateForm”的表单控件没有值访问器
- javascript - 如何仅使用 Javascript DOM 插入在 HTML 中重复的元素?
- python - 当将空列表作为函数的默认参数时,为什么 python 会显示这种行为?
- ruby-on-rails - rails瘦服务器无法启动-证书太小
- ios - UIKit 中的@State 属性
- python - 有没有办法为包含自身的类定义递归 __repr__ ?
- mongodb - 通过文档的 ID 使用 mongodb 更新文档