I am working on a React Native application which has a facebook login feature that is working, the action creator and reducer is working just fine for that, but the action creators and reducers for the job search API I am using is not working.
These are the action creators:
import axios from "axios";
// import { Location } from "expo";
import qs from "qs";
import { FETCH_JOBS, LIKE_JOB } 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 = () => {
const query = qs.stringify({ ...JOB_QUERY_PARAMS });
return `${JOB_ROOT_URL}${query}`;
};
export const fetchJobs = (
region,
distance = 10,
callback
) => 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 });
callback();
} catch (e) {
console.log("fetchJobs Action Error:", e.message);
}
};
export const likeJob = job => {
return {
payload: job,
type: LIKE_JOB
};
};
This is the jobs_reducer.js
:
import { FETCH_JOBS } from "../actions/types";
const INITIAL_STATE = {
listings: []
};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_JOBS:
return action.payload;
default:
return state;
}
}
This is the likes_reducer.js
:
import _ from "lodash";
import { LIKE_JOB } from "../actions/types";
export default function(state = [], action) {
switch (action.type) {
case LIKE_JOB:
return _.uniqBy([action.payload, ...state], "id");
default:
return state;
}
}
I spent all evening yesterday debugging this and all I know is that I am console logging data to my terminal, I can see the details of the jobs API data just populate my terminal, but the error message is that the reducers are returning undefined.
How can I return data and return undefined simultaneously?
I am using an asynchronous action creator. Do I need redux-thunk
on a React Native application? Am I using a version of React Native that has a bug right now? I know how Redux is supposed to work and so I am stumped here.
In reducers/index.js
:
import { combineReducers } from "redux";
import auth from "./auth_reducer";
import jobs from "./jobs_reducer";
import likedJobs from "./likes_reducer";
export default combineReducers({
auth,
jobs,
likedJobs
});
Where I start to get that error or what the error is referencing is the this.props.data.map()
as in the 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: () => {},
keyProp: "id"
};
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() {
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;
Above I am getting that this.props.data.map()
is either undefined or is not a function.
I get the same issue in ReviewScreen.js
:
import React, { Component } from "react";
import { View, Text, Button, ScrollView } from "react-native";
import { connect } from "react-redux";
class ReviewScreen extends Component {
static navigationOptions = ({ navigation }) => ({
headerTitle: "Review Jobs",
headerRight: (
<Button
title="Settings"
onPress={() => {
navigation.navigate("settings");
}}
/>
)
});
renderLikedJobs() {
return this.props.likedJobs.map(job => {
return (
<Card>
<View style={{ height: 200 }}>
<View style={styles.detailWrapper}>
<Text style={styles.italics}>{job.company}</Text>
<Text style={styles.italics}>{job.post_date}</Text>
</View>
</View>
</Card>
);
});
}
render() {
return <ScrollView>{this.renderLikedJobs()}</ScrollView>;
}
}
const styles = {
detailWrapper: {
marginBottom: 10,
flexDirection: "row",
justifyContent: "space-around"
}
};
function mapStateToProps(state) {
return { jobs: state.jobs };
}
export default connect(mapStateToProps)(ReviewScreen);
Same thing above with this.props.likedJobs.map()
2
Answers
I ended up refactoring my jobs reducer like so:
Instead of calling your API, in your action creator, Hardcore the API data and return it. If your reducer keeps returning undefined the error is in your code.
For async calls you need to use redux-thunk or redux-saga, i would recommend you redux-saga because it avoids callback hell.