反应本机TypeError:TypeError:undefined不是一个对象(评估'this.props.data.map')

时间:2019-05-12 03:26:32

标签: javascript reactjs react-native reusability

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

有问题的可重用组件正在迭代the_var,该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);

这应该是一个对象数组,如此处所示: enter image description here

在我的减速器中,我有:

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”,减速器   “工作”返回未定义。要忽略动作,必须明确   返回先前的状态。如果您希望该减速器不具有任何价值,   您可以返回null而不是未定义。

所以看来问题出在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,但仍然遇到该错误,所以我困惑于确切的位置,因为我确实将初始状态设置为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();
  }

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

3 个答案:

答案 0 :(得分:0)

道具不能立即从渲染器上的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) => {
...

答案 1 :(得分:0)

map 函数通常会遍历数组对象。您正在尝试遍历非数组对象。因此,首先使用<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script> <nav class="navbar navbar-expand-md bg-dark navbar-dark bg-custom sticky-top"> <div class="container"> <a class="navbar-brand" href="#"> <i class='far fa-image' style='font-size:24px'></i> IMGS</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a class="nav-link" href="#">About</a> </li> <li class="nav-item"> <a class="nav-link" href="#">Contact</a> </li> </ul> <ul class="navbar-nav mr-0"> <li class="nav-item"> <a class="nav-link" href="#">Sign Up</a> </li> <li class="nav-item"> <a class="nav-link" href="#">Login</a> </li> </ul> </div> </div> </nav> <div class="container my-3"> <div class="jumbotron py-4 mb-3"> <h1> <i class='fas fa-camera-retro' style='font-size:40px'></i> The Image Gallery</h1> <p>A bunch of beautiful images that I didn't take(except the first one!)</p> </div> <div class="row "> <div class="col-sm-6 col-md-4 my-2"> <img src="https://images.unsplash.com/photo-1481889617387-82a8f2413b6b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60" alt="" class="img-thumbnail mx-auto d-block img-fluid"> </div> <div class="col-sm-6 col-md-4 my-2"> <img src="https://images.unsplash.com/photo-1430026996702-608b84ce9281?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60" alt="" class="img-thumbnail mx-auto d-block img-fluid"> </div> <div class="col-sm-6 col-md-4 my-2"> <img src="https://images.unsplash.com/photo-1551356279-8337da8863d3?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60" alt="" class="img-thumbnail mx-auto d-block img-fluid"> </div> <div class="col-sm-6 col-md-4 my-2"> <img src="https://images.unsplash.com/photo-1551897628-d053c089e823?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60" alt="" class="img-thumbnail mx-auto d-block img-fluid"> </div> <div class="col-sm-6 col-md-4 my-2"> <img src="https://images.unsplash.com/photo-1555939765-9b78f509d500?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60" alt="" class="img-thumbnail mx-auto d-block img-fluid"> </div> <div class="col-sm-6 col-md-4 my-2"> <img src="https://images.unsplash.com/photo-1516911588919-bf6b970eaf32?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60" alt="" class="img-thumbnail mx-auto d-block img-fluid"> </div> <div class="col-sm-6 col-md-4 my-2"> <img src="https://images.unsplash.com/photo-1551222998-8a604fda1d22?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60" alt="" class="img-thumbnail mx-auto d-block img-fluid"> </div> <div class="col-sm-6 col-md-4 my-2"> <img src="https://images.unsplash.com/photo-1550977088-b31835bf616d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60" alt="" class="img-thumbnail mx-auto d-block img-fluid"> </div> <div class="col-sm-6 col-md-4 my-2"> <img src="https://images.unsplash.com/photo-1551290465-0ac783e20542?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60" alt="" class="img-thumbnail mx-auto d-block img-fluid"> </div> </div> </div>检查对象的类型,然后使用函数。

答案 2 :(得分:0)

看起来像是从以下位置重构了我的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;
  }
}