uncaught error, 'updatedDoc 'is read-only - javascript

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;

Related

undefined is not an object (evaluating 'selectedDoc.quantity')

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
}

undefined is not an object(evaluating 'addedProduct.price').. I don't know why I am getting this error?

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.

React Native TypeError: TypeError: undefined is not an object (evaluating 'this.props.data.map')

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

React-native: how to render inputs based on a number selected on a previous screen and be able to store the inputs

I have a screen where the user selects the number of players for a game:
On the next screen, some text inputs appear and I want to be able to name the players so these names can be stored in a database:
I am using a FlatList to generate these fields, but it seems that I can't use the onChangeText here. If I try to input text into any of the fields I get this error:
This is the code:
import React, {Component} from 'react';
import {View, Text, StyleSheet, ScrollView, Image, FlatList} from 'react-native';
import colors from '../config/colors';
import {TextField} from '../components/TextField';
import {PrimaryButton} from '../components/PrimaryButton';
import AsyncStorage from '#react-native-community/async-storage';
class player_names_screen extends Component {
constructor (props) {
super(props);
this.state = {
num_players: [],
Player1: '',
Player2: '',
Player3: '',
Player4: '',
Player5: '',
Player6: '',
Player7: '',
Player8: ''
}
}
componentDidMount = () => {
this.getNumPlayers();
}
//This is my nasty way of generating the correct amount of text fields based on the number selected
getNumPlayers = async () => {
let players = await AsyncStorage.getItem('Num_Players');
this.setState({number_players: players});
var players_json_middle = '';
var x;
for (x = 1; x <= players; x++) {
player = '{"Player": "Player ';
num_str = x.toString();
if (x != players) {
var player_num = player.concat(num_str, '"},');
} else {
var player_num = player.concat(num_str, '"}');
}
players_json_middle = players_json_middle.concat(player_num);
}
var players_json_start = '{"Players":[';
var players_json_end = ']}';
var players_json_str = players_json_start.concat(players_json_middle, players_json_end);
var players_json = JSON.parse(players_json_str);
this.setState({num_players: players_json.Players});
}
renderItem = ({item}) => {
return (
<TextField
placeholder={item.Player}
//This is my main problem. It isn't the way to change the state of the player names.
onChangeText={() => this.setState(item.Player)}
/>
)
}
signUp = async () => {
let user = await AsyncStorage.getItem('email');
const {Player1} = this.state;
const {Player2} = this.state;
const {Player3} = this.state;
const {Player4} = this.state;
const {Player5} = this.state;
const {Player6} = this.state;
const {Player7} = this.state;
const {Player8} = this.state;
fetch('fetch address is here [this isn't the problem]', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application.json',
},
body: JSON.stringify({
email: user,
player1: Player1,
player2: Player2,
player3: Player3,
player4: Player4,
player5: Player5,
player6: Player6,
player7: Player7,
player8: Player8
})
}).then((response) => response.json())
.then((responseJson) => {
this.props.navigation.navigate('round_start_screen');
}).catch((error) => {
console.error(error);
});
}
render() {
return (
<ScrollView>
<View style={{alignItems: 'center'}}>
<Text style={styles.headingText}>
Game Name
</Text>
</View>
<View style={styles.container}>
<Text style={styles.text}>
What are the players' names?
</Text>
<View style={{width: '80%'}}>
//Rendering the flatlist here
<FlatList
data={this.state.num_players}
renderItem={this.renderItem}
keyExtractor={(item, index) => index.toString()}
/>
</View>
<PrimaryButton
onPress={() => this.signUp()}
label="Start Game"
>
</PrimaryButton>
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
backgroundColor: colors.backgroundColor,
margin: 10,
paddingBottom: 5,
borderWidth: 1,
borderColor: colors.borderColor,
},
text: {
fontSize: 24,
color: colors.primaryText,
marginTop: 10,
},
headingText: {
fontSize: 24,
fontWeight: '500',
color: colors.primaryText,
margin: 10,
},
})
export default player_names_screen;
Is there a way to be able to name the players here and store them? Or is this whole approach an exercise in futility? Is there a much better way to do it?
You havent provided a key to update. You also need to use the text value that changed.
renderItem = ({item, index}) => {
return (
<TextField
placeholder={item.Player}
onChangeText={(newText) => this.setState({[`Player${index}`]: newText})
/>
)
}
You might want to consider using an array for this state though. It would enable you to update the array at the index position, instead of doing string interpolation.
If you need a fast solution, you can add a switch case as follows,
renderItem = ({item, index}) => {
return (
<TextField
placeholder={item.Player}
onChangeText={() => this._updateState(item, index)}
/>
)
}
_updateState = (item, index) => {
switch(index) {
case 0: this.setState({Player1 :item.Player}); break;
case 1: this.setState({Player2 :item.Player}); break;
case 2: this.setState({Player3 :item.Player}); break;
case 3: this.setState({Player4 :item.Player}); break;
case 4: this.setState({Player5 :item.Player}); break;
case 5: this.setState({Player6 :item.Player}); break;
case 6: this.setState({Player7 :item.Player}); break;
case 7: this.setState({Player8 :item.Player}); break
}
}

I can not send the text to firebase when Button is click with redux

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

Categories