React Native TypeError: TypeError: undefined is not an object (evaluating 'this.props.data.map')

React Native TypeError: TypeError: undefined is not an object (evaluating 'this.props.data.map')

我决定重用我认为适用于我的新应用程序的组件,该应用程序正在引入第三方 API。

有问题的可重用组件正在迭代 this.props.data.map(),它在我的 components/Swipe.js 文件中评估为未定义:

import React, { Component } from "react";
import {
  View,
  Animated,
  PanResponder,
  Dimensions,
  LayoutAnimation,
  UIManager
} from "react-native";

const SCREEN_WIDTH = Dimensions.get("window").width;
const SWIPE_THRESHOLD = 0.25 * SCREEN_WIDTH;
const SWIPE_OUT_DURATION = 250;

class Swipe extends Component {
  static defaultProps = {
    onSwipeRight: () => {},
    onSwipeLeft: () => {}
  };

  constructor(props) {
    super(props);

    const position = new Animated.ValueXY();
    const panResponder = PanResponder.create({
      onStartShouldSetPanResponder: (event, gestureState) => true,
      onPanResponderMove: (event, gestureState) => {
        position.setValue({ x: gestureState.dx, y: gestureState.dy });
      },
      onPanResponderRelease: (event, gestureState) => {
        if (gestureState.dx > SWIPE_THRESHOLD) {
          this.forceSwipe("right");
        } else if (gestureState.dx < -SWIPE_THRESHOLD) {
          this.forceSwipe("left");
        } else {
          this.resetPosition();
        }
      }
    });

    this.state = { panResponder, position, index: 0 };
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.data !== this.props.data) {
      this.setState({ index: 0 });
    }
  }

  componentWillUpdate() {
    UIManager.setLayoutAnimationEnabledExperimental &&
      UIManager.setLayoutAnimationEnabledExperimental(true);
    LayoutAnimation.spring();
  }

  forceSwipe(direction) {
    const x = direction === "right" ? SCREEN_WIDTH : -SCREEN_WIDTH;
    Animated.timing(this.state.position, {
      toValue: { x, y: 0 },
      duration: SWIPE_OUT_DURATION
    }).start(() => this.onSwipeComplete(direction));
  }

  onSwipeComplete(direction) {
    const { onSwipeLeft, onSwipeRight, data } = this.props;
    const item = data[this.state.index];
    direction === "right" ? onSwipeRight(item) : onSwipeLeft(item);
    this.state.position.setValue({ x: 0, y: 0 });
    this.setState({ index: this.state.index + 1 });
  }

  resetPosition() {
    Animated.spring(this.state.position, {
      toValue: { x: 0, y: 0 }
    }).start();
  }

  getCardStyle() {
    const { position } = this.state;
    const rotate = position.x.interpolate({
      inputRange: [-SCREEN_WIDTH * 1.5, 0, SCREEN_WIDTH * 1.5],
      outputRange: ["-120deg", "0deg", "120deg"]
    });
    return {
      ...position.getLayout(),
      transform: [{ rotate }]
    };
  }

  renderCards() {
    console.log(this.props);
    if (this.state.index >= this.props.data.length) {
      return this.props.renderNoMoreCards();
    }
    return this.props.data
      .map((item, i) => {
        if (i < this.state.index) {
          return null;
        }
        if (i === this.state.index) {
          return (
            <Animated.View
              key={item[this.props.id]}
              style={[this.getCardStyle(), styles.cardStyle]}
              {...this.state.panResponder.panHandlers}
            >
              {this.props.renderCard(item)}
            </Animated.View>
          );
        }
        return (
          <Animated.View
            key={item[this.props.id]}
            style={[styles.cardStyle, { top: 10 * (i - this.state.index) }]}
          >
            {this.props.renderCard(item)}
          </Animated.View>
        );
      })
      .reverse();
  }

  render() {
    return <View>{this.renderCards()}</View>;
  }
}

const styles = {
  cardStyle: {
    position: "absolute",
    width: SCREEN_WIDTH
  }
};

export default Swipe;

我不清楚为什么会这样,因为我确实在我的动作创建器中取回了 payload: data

export const fetchJobs = (region, callback) => async dispatch => {
  try {
    const url =
      JOB_ROOT_URL +
      JOB_QUERY_PARAMS.key +
      "&method=" +
      JOB_QUERY_PARAMS.method +
      "&category=" +
      JOB_QUERY_PARAMS.keyword +
      "&format=" +
      JOB_QUERY_PARAMS.format;
    let { data } = await axios.get(url);
    dispatch({ type: FETCH_JOBS, payload: data });
    callback();
  } catch (e) {
    console.log(e);
  }
};

那么为什么 data 在我的可重用组件中评估为未定义?

DeckScreen.js这里被调用:

import React, { Component } from "react";
import { View, Text } from "react-native";
import { connect } from "react-redux";
import { MapView } from "expo";
import { Card, Button } from "react-native-elements";
import Swipe from "../components/Swipe";

class DeckScreen extends Component {
  renderCard(job) {
    return (
      <Card title={job.title}>
        <View style={styles.detailWrapper}>
          <Text>{job.company}</Text>
          <Text>{job.post_date}</Text>
        </View>
        <Text>
          {job.description.replace(/<span>/g, "").replace(/<\/span>/g, "")}
        </Text>
      </Card>
    );
  }

  render() {
    return (
      <View>
        <Swipe data={this.props.jobs} renderCard={this.renderCard} />
      </View>
    );
  }
}

const styles = {
  detailWrapper: {
    flexDirection: "row",
    justifyContent: "space-around",
    marginBottom: 10
  }
};

function mapStateToProps({ jobs }) {
  return { jobs: jobs.listing };
}

export default connect(mapStateToProps)(DeckScreen);

我按下的按钮出现此错误是在 MapScreen 屏幕中:

import React, { Component } from "react";
import { View, Text, ActivityIndicator } from "react-native";
import { Button } from "react-native-elements";
import { MapView } from "expo";
import { connect } from "react-redux";

import * as actions from "../actions";

class MapScreen extends Component {
  state = {
    region: {
      longitude: 30.2672,
      latitude: 97.7431,
      longitudeDelta: 0.04,
      latitudeDelta: 0.09
    }
  };

  onButtonPress = () => {
    this.props.fetchJobs(this.state.region, () => {
      this.props.navigation.navigate("deck");
    });
  };

  getLocationHandler = () => {
    navigator.geolocation.getCurrentPosition(pos => {
      const currentCoords = {
        longitude: pos.coords.longitude,
        latitude: pos.coords.latitude
      };

      this.goToLocation(currentCoords);
    });
  };

  goToLocation = coords => {
    this.map.animateToRegion({
      ...this.state.region,
      longitude: coords.longitude,
      latitude: coords.latitude
    });
    this.setState(prevState => {
      return {
        region: {
          ...prevState.region,
          longitude: coords.longitude,
          latitude: coords.latitude
        }
      };
    });
  };

  render() {
    return (
      <View style={{ flex: 1 }}>
        <MapView
          initialRegion={this.state.region}
          style={{ flex: 1 }}
          ref={ref => (this.map = ref)}
        />
        <View style={styles.buttonContainer}>
          <Button
            title="Search This Area"
            icon={{ name: "search" }}
            onPress={this.onButtonPress}
          />
        </View>
        <View>
          <Button
            title="My Location"
            icon={{ name: "map" }}
            onPress={this.getLocationHandler}
          />
        </View>
      </View>
    );
  }
}

const styles = {
  buttonContainer: {
    position: "absolute",
    bottom: 50,
    left: 0,
    right: 0
  }
};

export default connect(
  null,
  actions
)(MapScreen);

这应该是一个对象数组,如此处验证:

在我的减速器中我有:

import { FETCH_JOBS } from "../actions/types";

const INITIAL_STATE = {
  listing: []
};

export default function(state = INITIAL_STATE, action) {
  switch (action.type) {
    case FETCH_JOBS:
      return action.payload;
    default:
      return state;
  }
}

我添加了一些详细的错误处理,这就是我得到的结果:

[02:25:28] fetchJobs Action Error: Given action "fetch_jobs", reducer "jobs" returned undefined. To ignore an action, you must explicitly return the previous state. If you want this reducer to hold no value, you can return null instead of undefined.

看来问题出在 jobs_reducer:

import { FETCH_JOBS } from "../actions/types";

const INITIAL_STATE = {
  listing: []
};

export default function(state = INITIAL_STATE, action) {
  switch (action.type) {
    case FETCH_JOBS:
      return action.payload;
    default:
      return state;
  }
}

我不知道我现在是不是太累了,但我已经尝试了 listings: [],我已经尝试了 listing: [],我不知道如何获得这个减速器不是 return undefined 因为即使我这样做:

import { FETCH_JOBS } from "../actions/types";

// const INITIAL_STATE = {
//   listing: []
// };

export default function(state = null, action) {
  switch (action.type) {
    case FETCH_JOBS:
      return action.payload;
    default:
      return state;
  }
}

我收到同样的错误消息。

我创建 INITIAL_STATE 并将其设置为 listing: [] 的想法是确保我可以映射这个数组,而不必担心我尚未获取作业列表的情况。

所以我很困惑我在哪里得到这个未定义的,因为我确实将初始状态设置为 null 但我仍然收到那个错误。

所以在调试的过程中我又尝试了这个:

import { FETCH_JOBS } from "../actions/types";

// const INITIAL_STATE = {
//   listing: []
// };

export default function(state = null, action) {
  console.log("action is", action);
  switch (action.type) {
    case FETCH_JOBS:
      return action.payload;
    default:
      return state;
  }
}

并得知 payload 未定义:

Please check your inputs.
[09:39:38] action is Object {
[09:39:38]   "payload": undefined,
[09:39:38]   "type": "fetch_jobs",
[09:39:38] }

我在这里碰壁了。我对 jobs 动作创建器进行了整体重构并注销了 payload 属性:

export const fetchJobs = (region, distance = 10) => async dispatch => {
  try {
    const url = buildJobsUrl();
    let job_list = await axios.get(url);
    job_list = locationify(
      region,
      console.log(job_list.data.listings.listing),
      job_list.data.listings.listing,
      distance,
      (obj, coords) => {
        obj.company.location = { ...obj.company.location, coords };
        return obj;
      }
    );
    dispatch({ type: FETCH_JOBS, payload: job_list });
  } catch (e) {
    console.log("fetchJobs Action Error:", e.message);
  }
};

console.log(job_list.data.listings.listing) 成功将数据注销到我的终端,但我的 payload 属性 仍然未定义,这怎么可能?

我通过将 action creator 重构为这个来让 action creator 和 reducer 工作:

import axios from "axios";
import { Location } from "expo";
import qs from "qs";

import { FETCH_JOBS } from "./types";
// import locationify from "../tools/locationify";

const JOB_ROOT_URL = "https://authenticjobs.com/api/?";

const JOB_QUERY_PARAMS = {
  api_key: "<api_key>",
  method: "aj.jobs.search",
  perpage: "10",
  format: "json",
  keywords: "javascript"
};

const buildJobsUrl = zip => {
  const query = qs.stringify({ ...JOB_QUERY_PARAMS });
  return `${JOB_ROOT_URL}${query}`;
};

export const fetchJobs = (region, callback) => async dispatch => {
  try {
    let zip = await Location.reverseGeocodeAsync(region);
    const url = buildJobsUrl(zip);
    console.log(url);
    let { data } = await axios.get(url);
    dispatch({ type: FETCH_JOBS, payload: data });
    callback();
  } catch (e) {
    console.error(e);
  }
};

所以理论上已经没有问题了吧。然后,当我引入 Swipe.js 组件时,问题 returns,特别是问题似乎与此代码有关:

renderCards() {
    if (this.state.index >= this.props.data.length) {
      return this.props.renderNoMoreCards();
    }

    return this.props.data
      .map((item, i) => {
        if (i < this.state.index) {
          return null;
        }

        if (i === this.state.index) {
          return (
            <Animated.View
              key={item[this.props.id]}
              style={[this.getCardStyle(), styles.cardStyle]}
              {...this.state.panResponder.panHandlers}
            >
              {this.props.renderCard(item)}
            </Animated.View>
          );
        }
        return (
          <Animated.View
            key={item[this.props.id]}
            style={[styles.cardStyle, { top: 10 * (i - this.state.index) }]}
          >
            {this.props.renderCard(item)}
          </Animated.View>
        );
      })
      .reverse();
  }

这又是我开始遇到障碍的地方。

props 不能立即从 redux store on render 获得,它是异步的。 select 来自 redux store 的数据最好使用 save navigation:

const mapStateToProps = state => ({
  jobs: state && state.jobs && state.jobs.listing
})

然后再次渲染以检查数据是否存在:

...
render() {
   const { jobs } = this.props;
   return (
     <View>
      {jobs && <Swipe data={jobs} renderCard={this.renderCard} />}
    </View>

}

...

renderCards() {
  const { data } = this.props;
  return data && data.map((item, index) => {
...

map函数一般遍历array-object。您正在尝试遍历 non-array 对象。所以首先使用 typeof(variable) 检查对象的类型,然后使用函数。

看起来有用的是重构我的 jobs_reducer 文件来自:

import { FETCH_JOBS } from "../actions/types";

const INITIAL_STATE = {
  listing: []
};

export default function(state = INITIAL_STATE, action) {
  switch (action.type) {
    case FETCH_JOBS:
      return action.payload;
    default:
      return state;
  }
}

对此:

export default function(state = INITIAL_STATE, action) {
  switch (action.type) {
    case FETCH_JOBS:
      const { listings } = action.payload;
      return { ...state, listing: listings.listing };
    default:
      return state;
  }
}