React Native TypeError: TypeError: undefined не является объектом (оценка 'this.props.data.map') - PullRequest
1 голос
/ 12 мая 2019

Я решил повторно использовать компонент, который, как я думал, будет работать для моего нового приложения, использующего сторонний 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);

Кнопка, которую я нажимаю, которая дает мне эту ошибку, находится в MapScreenscreen:

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", редуктор "jobs" вернул неопределенное значение.Чтобы игнорировать действие, вы должны явно вернуть предыдущее состояние.Если вы хотите, чтобы этот редуктор не содержал значения, вы можете вернуть 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;
  }
}

Iне знаю, слишком ли я истощен на данный момент, но я пытался 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: [] состоит в том, чтобы я мог отобразить этот массив иникогда не беспокойтесь о случае, когда я еще не получил список заданий.

Так что я озадачен тем, где именно я получаю это неопределенное значение, так как я установил начальное состояние на ноль, и я все еще получал этоошибка.

Итак, в процессе отладки я попытался это сделать:

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 голосов
/ 12 мая 2019

map Функция обычно перебирает массив-объект.Вы пытаетесь перебрать объект без массива.Поэтому сначала проверьте тип объекта, используя typeof(variable), затем используйте функцию.

0 голосов
/ 30 мая 2019

Похоже, что помогла рефакторинг моего 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;
  }
}
0 голосов
/ 12 мая 2019

Реквизиты не доступны сразу из магазина приставок при рендеринге, это происходит асинхронно.Чтобы выбрать данные из хранилища резервов, лучше использовать сохранить навигацию :

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