【问题标题】:React Native TypeError: TypeError: undefined is not an object (evaluating 'this.props.data.map')React Native TypeError:TypeError:未定义不是对象(评估'this.props.data.map')
【发布时间】:2019-09-29 10:45:26
【问题描述】:

我决定重用一个我认为适用于我的新应用程序的组件,该应用程序正在引入第三方 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 操作错误:给定操作“fetch_jobs”,reducer “工作”返回未定义。要忽略一个动作,你必须明确 返回之前的状态。如果你想让这个减速器没有任何价值, 你可以返回 null 而不是 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: [],我不知道如何让这个减速器不返回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 属性仍然未定义,这怎么可能?

我通过将动作创建器重构为这样来使动作创建器和减速器工作:

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 组件时,问题又回来了,特别是这里的代码似乎有问题:

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();
  }

这是我再次遇到障碍的地方。

【问题讨论】:

  • 似乎DeckScreen 中的this.props.jobs 可能未定义。您会发布如何将属性设置为DeckScreen 元素吗?
  • @JuanScolari,我将所有代码发布到DeckScreenMapScreen

标签: javascript reactjs react-native reusability


【解决方案1】:

props 在渲染时不能立即从 redux 商店获得,它是异步的。 要更好地从 redux 存储中选择数据以使用 保存导航

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) => {
...

【讨论】:

  • 我试过mapStateToProps = state =&gt; { return { jobs: state.jobs.listings }; };,没有用。
  • 您也需要检查渲染。我稍微完成了我的回答。
  • 当我在上面运行您的代码时,我收到消息ReferenceError: Can't find variable: jobs
  • 对不起,我忘了从这个道具中解构它。我更正了。
  • 我听从了你的回答,这次我确实将所有数据都记录到了我的终端上,但在移动设备 UI 上我最终得到了同样的错误。不知道该怎么做。
【解决方案2】:

ma​​p 函数一般会遍历数组对象。您正在尝试遍历非数组对象。所以首先使用typeof(variable)检查对象的类型,然后使用函数。

【讨论】:

  • 您在哪里看到我正在尝试遍历非数组?
  • 您正在从端点获取数据并作为有效负载进行调度。请检查您从端点获取的数据是否未定义或非数组对象。使用 console.log(data) 然后查看数据类型。
【解决方案3】:

看起来帮助重构了我的 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;
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-05
    • 2021-11-09
    相关资源
    最近更新 更多