I am constantly getting this error - This issue is in remove case. as soon as I remove the remove case in Appoint/reducers, the code is working. can someone help me find what the error is in remove case.
undefined is not an object (evaluating 'selectedDoc.quantity')
I don't know why it's undefined. help will be appreciated.
//appoint.js/reducers
import { APPOINT_DOC,Remove_Doc } from "../actions/appoint";
import docAppoint from '../../modules/docAppoint';
const initialState = {
items: {},
totalAmount: 0
};
export default (state = initialState, action) => {
switch (action.type) {
case APPOINT_DOC:
const addedProduct = action.product;
const prodPrice = addedProduct.price;
const prodTitle = addedProduct.title;
let updateOrNewDocAppoint;
if (state.items[addedProduct.id]) {
// already have the item in the cart
updateOrNewDocAppoint = new docAppoint(
state.items[addedProduct.id].quantity + 1,
prodPrice,
prodTitle,
state.items[addedProduct.id].sum + prodPrice
);
} else {
updateOrNewDocAppoint = new docAppoint(1, prodPrice, prodTitle, prodPrice);
}
return {
...state,
items: { ...state.items, [addedProduct.id]: updateOrNewDocAppoint },
totalAmount: state.totalAmount + prodPrice
};
case Remove_Doc:
const selectedDoc = state.items[action.pid];
const quantity = selectedDoc.quantity;
let updatedDoc;
if (quantity > 1) {
// need to reduce it, not erase it
const updatedDoc = new docAppoint(
selectedDoc.quantity - 1,
selectedDoc.prodPrice,
selectedDoc.prodTitle,
selectedDoc.sum - selectedDoc.prodPrice
);
updatedDoc = { ...state.items, [action.pid]: updatedDoc };
} else {
updatedDoc = { ...state.items };
delete updatedDoc[action.pid];
}
return {
...state,
items: updatedDoc,
totalAmount: state.totalAmount - selectedDoc.prodPrice
};
}
//appoint.js/actions
export const APPOINT_DOC = 'APPOINT_DOC';
export const Remove_Doc = 'Remove_Doc';
export const appointDoc = product => {
return {
type : APPOINT_DOC, product:product
};
};
export const removeDoc = productId => {
return{ type: Remove_Doc, pid: productId };
};
//docAppoint.js/modules
class docAppoint {
constructor( quantity,productPrice,productTitle,sum){
this.quantity=quantity;
this.productPrice=productPrice;
this.productTitle=productTitle;
this.sum=sum;
}
}
//registerDr
import React from "react";
import { View, Text, StyleSheet,Button,FlatList } from 'react-native';
import {useSelector,useDispatch} from 'react-redux';
import colors from "../../constants/colors";
import appoint from "../../store/reducers/appoint";
import ProcessDr from "../../components/Doctor/process";
import * as docActions from '../../store/actions/appoint';
const registerDr = props => {
const totalFee = useSelector (state => state.appoint.totalAmount)
const appointDoc = useSelector (state => {
const transformedAppointDoc = []
for (const key in state.appoint.items ) {
transformedAppointDoc.push({
productId : key,
productTitle : state.appoint.items[key].productTitle,
productPrice : state.appoint.items[key].productPrice,
quantity : state.appoint.items[key].quantity,
sum : state.appoint.items[key].sum
});
}
return transformedAppointDoc;
});
const dispatch = useDispatch();
return(
<View style={styles.screen}>
<View style={styles.summary}>
<Text style={styles.summarytext}> Fee: <Text style={styles.amount} > {totalFee} RS </Text> </Text>
<Button disabled = {appointDoc.length === 0}
title="Appoint Doctor" color={colors.primary} />
</View>
<View>
<FlatList data ={appointDoc}
keyExtractor = {item => item.productId}
renderItem ={itemData =>
<ProcessDr quantity={itemData.item.quantity}
title={itemData.item.productTitle}
amount={itemData.item.sum}
onRemove = {() => {
dispatch(docActions.removeDoc(itemData.item.productTitle))
}}
/>} />
</View>
</View>
)
}
const styles= StyleSheet.create({
screen:{
margin: 20,
},
summary:{
flexDirection:'row',
alignItems:'center',
justifyContent:'space-between',
marginBottom:20,
padding: 10,
shadowColor: 'black',
shadowOpacity: 0.26,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 8,
elevation: 5,
borderRadius: 10,
backgroundColor: 'white',
},
summarytext:{
fontFamily:'opsb',
fontSize:18,
},
amount: {
color: colors.primary
}
})
export default registerDr;
at first glance, undefined is not an object (evaluating 'selectedDoc.quantity') is referring to the selectedDoc is undefined.
Have you debugged to see if the items has the the correct key that matches action.pid ?
const selectedDoc = state.items[action.pid];
const quantity = selectedDoc.quantity;
do nothing if undefined.
const selectedDoc = state.items[action.pid];
if (selectedDoc) {
// your logic here
} else {
return state; // do nothing
}
Related
when running the code below, i get uncaught error, 'updatedDoc 'is read-only error.
i am fairly new to reactnative, and I can't figure out where this error is.
when i am pressing processDr function, triggering the onPress event, I am getting this error,I would appreciate if you help me here. I am new to react native development.
//appoint.js/reducers
import { APPOINT_DOC,Remove_Doc } from "../actions/appoint";
import docAppoint from '../../modules/docAppoint';
const initialState = {
items: {},
totalAmount: 0
};
export default (state = initialState, action) => {
switch (action.type) {
case APPOINT_DOC:
const addedProduct = action.product;
const prodPrice = addedProduct.price;
const prodTitle = addedProduct.title;
let updateOrNewDocAppoint;
if (state.items[addedProduct.id]) {
// already have the item in the cart
updateOrNewDocAppoint = new docAppoint(
state.items[addedProduct.id].quantity + 1,
prodPrice,
prodTitle,
state.items[addedProduct.id].sum + prodPrice
);
} else {
updateOrNewDocAppoint = new docAppoint(1, prodPrice, prodTitle, prodPrice);
}
return {
...state,
items: { ...state.items, [addedProduct.id]: updateOrNewDocAppoint },
totalAmount: state.totalAmount + prodPrice
};
case Remove_Doc:
const selectedDoc = state.items[action.pid];
const quantity = selectedDoc.quantity;
let updatedDoc;
if (quantity > 1) {
// need to reduce it, not erase it
const updatedDoc = new docAppoint(
selectedDoc.quantity -1,
selectedDoc.prodPrice,
selectedDoc.prodTitle,
selectedDoc.sum - selectedDoc.prodPrice
);
updatedDoc = { ...state.items, [action.pid]: updatedDoc };
} else {
updatedDoc = { ...state.items };
delete updatedDoc[action.pid];
}
return {
...state,
items: updatedDoc,
totalAmount: state.totalAmount - selectedDoc.prodPrice
};
}
return state;
};
//registerDr
import React from "react";
import { View, Text, StyleSheet,Button,FlatList } from 'react-native';
import {useSelector,useDispatch} from 'react-redux';
import colors from "../../constants/colors";
import appoint from "../../store/reducers/appoint";
import ProcessDr from "../../components/Doctor/process";
import * as docActions from '../../store/actions/appoint';
const registerDr = props => {
const totalFee = useSelector (state => state.appoint.totalAmount)
const appointDoc = useSelector (state => {
const transformedAppointDoc = []
for (const key in state.appoint.items ) {
transformedAppointDoc.push({
productId : key,
productTitle : state.appoint.items[key].productTitle,
productPrice : state.appoint.items[key].productPrice,
quantity : state.appoint.items[key].quantity,
sum : state.appoint.items[key].sum
});
}
return transformedAppointDoc;
});
const dispatch = useDispatch();
return(
<View style={styles.screen}>
<View style={styles.summary}>
<Text style={styles.summarytext}> Fee: <Text style={styles.amount} > {totalFee} RS </Text> </Text>
<Button disabled = {appointDoc.length === 0}
title="Appoint Doctor" color={colors.primary} />
</View>
<View>
<FlatList data ={appointDoc}
keyExtractor = {item => item.productId}
renderItem ={itemData =>
<ProcessDr quantity={itemData.item.quantity}
title={itemData.item.productTitle}
amount={itemData.item.sum}
onRemove = {() => {
dispatch(docActions.removeDoc(itemData.item.productId))
}}
/>} />
</View>
</View>
)
}
const styles= StyleSheet.create({
screen:{
margin: 20,
},
summary:{
flexDirection:'row',
alignItems:'center',
justifyContent:'space-between',
marginBottom:20,
padding: 10,
shadowColor: 'black',
shadowOpacity: 0.26,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 8,
elevation: 5,
borderRadius: 10,
backgroundColor: 'white',
},
summarytext:{
fontFamily:'opsb',
fontSize:18,
},
amount: {
color: colors.primary
}
})
export default registerDr;
My code looks fine as I have checked states and it showing me that the item I have added and deleted in the state is fine but I don't know why I am getting this error?
In CartScreen.js onRemove() is dispatching the action which is stored in the store/actions/cart.js to remove an item from the cart.
CartScreen.js
import React from "react";
import { View, Text, FlatList, Button, StyleSheet } from "react-native";
import { useSelector, useDispatch } from "react-redux";
import Colors from "../../constants/Colors";
import CartItem from "../../components/shop/CartItem";
import * as cartActions from "../../store/actions/cart";
const CartScreen = (props) => {
const cartTotalAmount = useSelector((state) => state.cart.totalAmount);
const dispatch = useDispatch();
const cartItems = useSelector((state) => {
const transformedCartItems = [];
for (const key in state.cart.items) {
transformedCartItems.push({
productId: key,
productTitle: state.cart.items[key].productTitle,
productPrice: state.cart.items[key].productPrice,
quantity: state.cart.items[key].quantity,
sum: state.cart.items[key].sum,
});
}
return transformedCartItems;
});
return (
<View style={styles.screen}>
<View style={styles.summary}>
<Text style={styles.summaryText}>
Total:{" "}
<Text style={styles.amount}>${cartTotalAmount.toFixed(2)}</Text>
</Text>
<Button
color={Colors.primary}
title="Order Now"
disabled={cartItems.length === 0}
/>
</View>
<FlatList
data={cartItems}
keyExtractor={(item) => item.productId}
renderItem={(itemData) => (
<CartItem
quantity={itemData.item.quantity}
title={itemData.item.productTitle}
amount={itemData.item.sum}
onRemove={() => {
dispatch(cartActions.removeFromCart(itemData.item.productId)); //this is the function to remove item from cart.
}}
/>
)}
/>
</View>
);
};
const styles = StyleSheet.create({
screen: {
margin: 20,
},
summary: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
marginBottom: 20,
padding: 10,
shadowColor: "black",
shadowOpacity: 0.26,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 8,
elevation: 5,
borderRadius: 10,
backgroundColor: "white",
},
summaryText: {
fontFamily: "openSansBold",
fontSize: 18,
},
amount: {
color: Colors.secondary,
},
});
export default CartScreen;
store/actions/cart.js:
import { ADD_TO_CART, REMOVE_FROM_CART } from "../actions/cart";
import CartItem from "../../models/cart-item";
const intialState = {
items: {},
totalAmount: 0,
};
export default (state = intialState, action) => {
switch (action.type) {
case ADD_TO_CART:
const addedProduct = action.product;
const prodPrice = addedProduct.price;
const prodTitle = addedProduct.title;
let updatedOrNewCartItem;
if (state.items[addedProduct.id]) {
updatedOrNewCartItem = new CartItem(
state.items[addedProduct.id].quantity + 1,
prodPrice,
prodTitle,
state.items[addedProduct.id].sum + prodPrice
);
} else {
updatedOrNewCartItem = new CartItem(1, prodPrice, prodTitle, prodPrice);
}
return {
...state,
items: { ...state.items, [addedProduct.id]: updatedOrNewCartItem },
totalAmount: state.totalAmount + prodPrice,
};
case REMOVE_FROM_CART:
const selectedCartItem = state.items[action.pid];
const currentQty = selectedCartItem.quantity;
let updatedCartItems;
if (quantity > 1) {
const updatedCartItem = new CartItem(
selectedCartItem.quantity - 1,
selectedCartItem.productPrice,
selectedCartItem.productTitle,
selectedCartItem.sum - selectedCartItem.productPrice
);
updatedCartItems = { ...state.items, [action.pid]: updatedCartItem };
} else {
updatedCartItems = { ...state.items };
delete updatedCartItems[action.pid];
}
return {
...state,
items: updatedCartItems,
totalAmount: state.totalAmount - selectedCartItem.productPrice,
};
default:
break;
}
return state;
};
In this code, addedProduct.price is in the addToCart() which is working fine. I can add the items in my cart but If I try to remove the item by calling onRemove() then it is giving me above error. I don't why and How to solve this issue?
I was returning the type of action ADD_To_Cart in removeFromCart() that's why I was getting this error.
Before:
export const ADD_TO_CART = "ADD_TO_CART";
export const REMOVE_FROM_CART = "REMOVE_FROM_CART";
export const addToCart = (products) => {
return { type: ADD_TO_CART, product: products };
};
export const removeFromCart = (productId) => {
return { type: ADD_TO_CART, pid: productId };
};
So, I have corrected the type of action returned in the removeFromCart().
export const ADD_TO_CART = "ADD_TO_CART";
export const REMOVE_FROM_CART = "REMOVE_FROM_CART";
export const addToCart = (products) => {
return { type: ADD_TO_CART, product: products };
};
export const removeFromCart = (productId) => {
return { type: REMOVE_FROM_CART, pid: productId };
};
Now It's working fine.
I'm trying to update a state from my Redux by triggering an action that calls out an API.
I have tried debugging by using a console log on actions and the reducer to see whether the states are changing.
The API returns an object and stores it into the reducer and it seems to work just fine but the Screen UI didn't re-render or update the reducer state.
I tried using useState React Hook function to test whether the issue comes from my component or the dispatch function and turns out the issue is from Redux dispatch hook.
In this case, when I press on my ToggleSwitch component, the action triggers the API and store the new value to the reducer. But the state on the UI side isn't updating.
Here's my code
reducer:
const light_list = (state: object = {}, action) => {
if (action.type === C.FETCH_ALL_LIGHTS) {
state = action.payload;
return state;
} else if (action.type === C.CHANGE_LIGHT_STATE) {
_.merge(state[action.id].state, action.payload);
return state;
} else {
return state;
}
}
action:
export const UpdateLightState = (lampID, jsondata) => async (dispatch, getState) => {
const state = getState();
const { id }: BridgePairedType = state.pairing_bridge;
const bridge: ConfigurationTypes = state.bridge_list[id];
const response = await axios({
url: `http://${bridge.ipaddress}/api/${bridge.username}/lights/${lampID}/state`,
method: 'PUT',
data: jsondata
});
if (response && response.data) {
var payload = {};
response.data.map((data) => {
let key = Object.keys(data.success)[0].substring(Object.keys(data.success)[0].lastIndexOf('/') + 1);
let value = Object.values(data.success)[0];
payload[key] = value;
})
dispatch({
type: C.CHANGE_LIGHT_STATE,
id: lampID,
payload: payload
})
}
}
Screen UI:
function ControlBulbScreen() {
const { colors } = theme;
const lampID = useNavigationParam('lampID');
const dispatch = useDispatch();
const light: LightTypes = useSelector(state => state.light_list);
const updatelight = useCallback((lampID: string, json: LightUpdateStates) => dispatch(UpdateLightState(lampID, json)), [dispatch]);
const bordercolor = { borderColor: colors.white }
return (
<Block style={styles.container}>
<Block flex={false} center row space="between" style={styles.header}>
<Text h1 googlebold>{light[lampID].name}</Text>
<ToggleSwitch
offColor="#DDDDDD"
onColor={theme.colors.secondary}
onToggle={() => updatelight(lampID, { on: !light[lampID].state.on })}
isOn={light[lampID].state.on}
/>
</Block>
</Block>
)
}
const styles = StyleSheet.create({
container: {
paddingHorizontal: theme.sizes.base * 2,
backgroundColor: theme.colors.background
},
header: {
marginTop: 40
},
textControl: {
textAlign: 'left',
fontSize: 16
},
controlrow: {
marginTop: 20,
marginBottom: 10
},
thumb: {
width: 25,
height: 25,
borderRadius: 25,
borderColor: 'white',
borderWidth: 3,
backgroundColor: theme.colors.secondary,
},
textInput: {
height: 30,
borderBottomWidth: .5,
borderRadius: 0,
borderWidth: 0,
color : theme.colors.white,
textAlign: 'left',
paddingBottom: 10
}
});
export default ControlBulbScreen;
I guess the issue is in your reducer. You should not mutate your state directly. What I suggest is to create a new state instead what you need to return in each case.
The following statement will do the job for you: const newState = {...state}.
Try the following:
const light_list = (state: object = {}, action) => {
if (action.type === C.FETCH_ALL_LIGHTS) {
return action.payload;
} else if (action.type === C.CHANGE_LIGHT_STATE) {
const newState = {...state};
_.merge(newState[action.id].state, action.payload);
return newState;
} else {
return state;
}
}
Read further about Immutable Update Patterns in the documentation.
I hope that helps!
I decide to reuse a component that I thought would work for my new application that is pulling in a third-party API.
The reusable component in question is iterating this.props.data.map() which is evaluating as being undefined in my components/Swipe.js file:
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;
I am unclear why this is happening since I do get back a payload: data in my action creator:
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);
}
};
So why is data evaluating as undefined in my reusable component?
It's being called here in 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);
The button I am pressing that gives me this error is in the MapScreen screen:
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);
This should be an array of objects as verified here:
And in my reducer I have:
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 added some verbose error handling and this is what I got back:
[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.
So it seems like the problem is in the 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 don't know if I am just too exhausted at this point, but I have tried listings: [], I have tried listing: [], I am out of ideas of how to get this reducer to not return undefined because even when I do this:
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;
}
}
I get the same error message.
My idea with creating an INITIAL_STATE and setting it to listing: [] is to ensure I could map over this array and never worry about the case where I have not yet fetched the list of jobs.
So I am perplexed as to exactly where I am getting this undefined since I did set the initial state to null and I was still getting that error.
So in the process of debugging I then tried this:
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;
}
}
And got that the payload is undefined:
Please check your inputs.
[09:39:38] action is Object {
[09:39:38] "payload": undefined,
[09:39:38] "type": "fetch_jobs",
[09:39:38] }
I have hit a wall here. I did a whole refactor to my jobs action creator and logged out the payload property:
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);
}
};
The console.log(job_list.data.listings.listing) logged out the data to my terminal successfully and yet my payload property is still undefined, how is that possible?
I got the action creator and reducer working by refactoring the action creator to just this:
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);
}
};
So the problem is no longer there in theory, right. Then, when I bring in the Swipe.js component, the problem returns, in particular the problem seems to be with this code here:
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();
}
This is where I start to hit a roadblock again.
Props are not available immediatelly from redux store on render, it comes asyncrounously.
To select data from redux store better to use save navigation:
const mapStateToProps = state => ({
jobs: state && state.jobs && state.jobs.listing
})
Than again on render to check if data exists or not:
...
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 function generally iterate through array-object. You are trying to iterate through a non-array object. So first check the type of the object using typeof(variable) then use the function.
Looks like what helped was refactoring my jobs_reducer file from:
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;
}
}
to this:
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;
}
}
if i write this onPress={() => this.submit()} instead onPress={() => this.onSendBtnPressed()} i can get the text value but if i just write onPress={() => this.onSendBtnPressed()} this i can't see the text on the message list
here is my chatui
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {KeyboardAvoidingView, Image, TouchableOpacity, ReactNative,
StyleSheet } from 'react-native';
import {Footer, FooterTab} from "native-base";
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-
view';
import { View, Title, Screen, ScrollView, TextInput,Text } from
'#shoutem/ui';
import Messages from '../containers/Messages';
import Input from '../containers/Input';
import { sendMessage } from '../actions';
const mapStateToProps = (state) => ({
chatHeight: state.chatroom.meta.height,
user: state.user,
});
class ChatUI extends Component {
constructor()
{
super();
this.state = {
text: '',
scrollViewHeight: 0,
inputHeight: 0
}
}
updateValue(text) {
console.warn(text)
}
componentDidMount() {
this.scrollToBottom(false);
}
componentDidUpdate() {
this.scrollToBottom();
}
onChangeText(text) {
this.setState({text: text})
};
onSendBtnPressed (text) {
return sendMessage(text, this.props.user)
this.textInput.clear();
Keyboard.dismiss();
}
onScrollViewLayout = (event) => {
const layout = event.nativeEvent.layout;
this.setState({
scrollViewHeight: layout.height
});
}
onInputLayout = (event) => {
const layout = event.nativeEvent.layout;
this.setState({
inputHeight: layout.height
});
}
scrollToBottom(animate = false) {
const { scrollViewHeight, inputHeight } = this.state,
{ chatHeight } = this.props;
const scrollTo = chatHeight - scrollViewHeight + inputHeight;
if (scrollTo > 0) {
this.refs.scroll.scrollToEnd();
}
}
_scrollToInput(reactRef) {
this.refs.scroll.scrollToFocusedInput(ReactNative.findNodeHandle(reactRef));
}
submit() {
let collection = {}
collection.text = this.state.text,
console.warn(collection);
}
render() {
return (
<Screen >
<Title style={{paddingTop: 25, paddingLeft: 10, borderBottomWidth: 0.5, backgroundColor: 'white', paddingBottom: 10}}>
<Image style={{width:80, height: 80}} source={require('../img/11.png')} />
<Text> GENERAL CHAT</Text>
</Title>
<KeyboardAwareScrollView ref="scroll"
onLayout={this.onScrollViewLayout}>
<Messages />
</KeyboardAwareScrollView>
<Footer style={{width:360,
height:30,
backgroundColor:'#fff',
borderRadius:20,
borderWidth: 0.5,
marginBottom: 3,
borderColor: 'gray',
}}>
<TextInput
onSubmitEditing = {this.onSubmitEditing}
multiline
onLayout={this.onInputLayout}
submitAction={this.onSendBtnPressed}
ref="input"
placeholder="Say something ..."
onChangeText={(text) => this.onChangeText(text, 'text')}
style={{backgroundColor: 'rgba(0,0,0,0)',
borderBottomWidth: 1.5, borderColor: '#f8f8f8', fontSize: 16, color: 'gray', paddingBottom:20}}
ref={input => { this.textInput = input; } }/>
</Footer>
<TouchableOpacity
onPress={() => this.onSendBtnPressed()}
style={{marginLeft:280, backgroundColor:'gray', width: 70, height: 30}}
title= "send"
>
</TouchableOpacity>
</Screen>
)
}
}
export default connect(mapStateToProps, {sendMessage})(ChatUI);
and here is my actions
import firebase from '../firebase';
import DeviceInfo from 'react-native-device-info';
import {Actions} from 'react-native-router-flux';
import FCM, { FCMEvent, NotificationType, WillPresentNotificationResult,
RemoteNotificationResult } from 'react-native-fcm';
import { Platform } from 'react-native';
export const addMessage = (msg) => ({
type: 'ADD_MESSAGE',
...msg
});
export const sendMessage = (text, user) => {
return function (dispatch) {
let msg = {
text: text,
time: Date.now(),
author: {
name: user.name,
avatar: user.avatar
}
};
const newMsgRef = firebase.database()
.ref('messages')
.push();
msg.id = newMsgRef.key;
newMsgRef.set(msg);
dispatch(addMessage(msg));
};
};
export const startFetchingMessages = () => ({
type: 'START_FETCHING_MESSAGES'
});
export const receivedMessages = () => ({
type: 'RECEIVED_MESSAGES',
receivedAt: Date.now()
});
export const fetchMessages = () => {
return function (dispatch) {
dispatch(startFetchingMessages());
firebase.database()
.ref('messages')
.orderByKey()
.limitToLast(20)
.on('value', (snapshot) => {
setTimeout(() => {
const messages = snapshot.val() || [];
dispatch(receiveMessages(messages))
}, 0);
});
}
}
export const receiveMessages = (messages) => {
return function (dispatch) {
Object.values(messages).forEach(msg => dispatch(addMessage(msg)));
dispatch(receivedMessages());
}
}
export const updateMessagesHeight = (event) => {
const layout = event.nativeEvent.layout;
return {
type: 'UPDATE_MESSAGES_HEIGHT',
height: layout.height
}
}
export const setUserName = (name) => {
return (dispatch) => {
dispatch({
type: 'SET_USER_NAME',
payload: name
});
};
};
export const setUserAvatar = (avatar) => ({
type: 'SET_USER_AVATAR',
avatar: avatar && avatar.length > 0 ? avatar : 'avatar.jpg'
});
export const login = () => {
return function (dispatch, getState) {
dispatch(startAuthorizing());
firebase.auth()
.signInAnonymously()
.then(() => {
const { name, avatar } = getState().user;
firebase.database()
.ref(`users/`)
.push({
name,
avatar
});
dispatch(userAuthorized());
dispatch(fetchMessages());
});
}
}
export const checkUserExists = () => {
return function (dispatch) {
dispatch(startAuthorizing());
firebase.auth()
.signInAnonymously()
.then(() => firebase.database()
.ref(`users/${DeviceInfo.getUniqueID()}`)
.once('value', (snapshot) => {
const val = snapshot.val();
if (val === null) {
dispatch(userNoExist());
}else{
dispatch(setUserName(val.name));
dispatch(setUserAvatar(val.avatar));
startChatting(dispatch);
}
}))
.catch(err => console.log(err))
}
}
const startChatting = function (dispatch) {
dispatch(userAuthorized());
dispatch(fetchMessages());
FCM.requestPermissions();
FCM.getFCMToken()
.then(token => {
console.log(token)
});
FCM.subscribeToTopic('secret-chatroom');
FCM.on(FCMEvent.Notification, async (notif) => {
console.log(notif);
if (Platform.OS === 'ios') {
switch (notif._notificationType) {
case NotificationType.Remote:
notif.finish(RemoteNotificationResult.NewData); //other
types available: RemoteNotificationResult.NewData,
RemoteNotificationResult.ResultFailed
break;
case NotificationType.NotificationResponse:
notif.finish();
break;
case NotificationType.WillPresent:
notif.finish(WillPresentNotificationResult.All); //other
types available: WillPresentNotificationResult.None
break;
}
}
});
FCM.on(FCMEvent.RefreshToken, token => {
console.log(token);
});
}
export const startAuthorizing = () => ({
type: 'USER_START_AUTHORIZING'
});
export const userAuthorized = () => ({
type: 'USER_AUTHORIZED'
});
export const userNoExist = () => ({
type: 'USER_NO_EXIST'
});
and MessageList
import React, { Component } from 'react';
import {
ListView, Text, Row, Image,
View, Subtitle, Caption, Heading
} from '#shoutem/ui';
import moment from 'moment';
const Message = ({ msg }) => (
<Row>
<Image styleName="small-avatar top"
source={{ uri: msg.author.avatar }} />
<View styleName="vertical">
<View styleName="horizontal space-between">
<Subtitle>{msg.author.name}</Subtitle>
<Caption>{moment(msg.time).from(Date.now())}</Caption>
</View>
<Text styleName="multiline">{msg.text}</Text>
</View>
</Row>
);
const MessageList = ({ messages, onLayout }) => (
<ListView data={messages}
autoHideHeader={true}
renderRow={msg => <Message msg={msg} />}
onLayout={onLayout}
/>
);
export default MessageList;
You are not passing a text variable into your onSendButtonPressed function. It looks like you should be calling it using the following syntax:
onPress={() => this.onSendBtnPressed(someText)}
Your onSendBtnPressed() function has the return statement on the first line, so the code will not be fired underneath the return statement (not related to your question, but hope to fix any other headaches...)