I am trying to open a modal from the 3rd element of the scrollview. Ive tried opening the modal without using state but had a problem closing it. Ive added state but I get a render error. This is a piece of my code
const dummy = [
{ gender: 'Female', id: '1' },
{ gender: 'Male', id: '2' },
{ gender: 'More', id: '3' },
];
const [year, setYear] = useState();
const [modalOpen, setModalOpen] = useState(false);
const [selectedId, setSelectedId] = useState(null);
const pressHandler = item => {
setSelectedId(item.id);
selectedId == 3 ? setModalOpen(true) : setModalOpen(false);
};
<View style={{ flex: 1, flexDirection: 'row', flexWrap: 'wrap' }}>
<ScrollView horizontal>
{dummy.map((item, index) => {
const bgColor =
item.id === selectedId ? COLORS.primary : COLORS.card;
return (
<View key={item.id}>
<TouchableOpacity
activeOpacity={0.8}
onPress={pressHandler(item)}
style={{
backgroundColor: bgColor,
borderRadius: 8,
alignItems: 'center',
marginLeft: 12,
marginTop: 12,
}}>
<Text
style={{
paddingVertical: 12,
paddingHorizontal: 20,
color: COLORS.white,
fontFamily: 'Gilroy-Medium',
fontSize: 16,
lineHeight: 24,
}}>
{item.gender}
</Text>
</TouchableOpacity>
{selectedId == 3 && (
<Modal
visible={modalOpen}
transparent={true}
animationType="slide">
<View style={styles.modalBox}>
<Text style={styles.modalHeader}>Gender Identity</Text>
<TouchableOpacity style={styles.modalSelect}>
<Text style={styles.gendSelect}>Non-Binary</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.modalSelect}>
<Text style={styles.gendSelect}>Prefer Not to Say</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.modalClose}
onPress={() => setModalOpen(false)}>
<Text style={styles.gendSelect}>Cancel</Text>
</TouchableOpacity>
</View>
</Modal>
)}
</View>
);
})}
</ScrollView>
</View>
but I get a render error from React saying
Too many re-renders. React limits the number of renders
to prevent an infinite loop
Because you call pressHandler when render. Just update onPress to:
onPress={() => pressHandler(item)}
Related
I am trying to add some new items on a list but when i print the list on console log it adds the item but it shows me undefined of name and description. Apparantly there is something wrong with the inputs but I cannot figure out why.
Also on the app itself the it shows that a new item is added but without data.
import React ,{useState}from 'react';
import { KeyboardAvoidingView, StyleSheet,Text,View,TextInput,TouchableOpacity,Keyboard,ScrollView } from 'react-native';
import Task from './components/task';
export default function App(){
const [recipeName,setRecipeName]=useState("");
const [descriptionItem, setDescriptionItem] = useState("");
const [items, setItems] = useState([
{ itemName: "Chicken", description: "chicken test", id: 0 }
]);
const handleAddButtonClick = () => {
const newItem = {
itemName: recipeName, // change
descriptionItem: descriptionItem,
id: items.length
};
console.log(newItem);
const newItems = [...items, newItem];
setItems((state) => {
console.log(state);
console.log(newItems);
return newItems;
});
// setRecipeName("");
// setDescriptionItem("");
// console.log(items.description);
// console.log(items.id); //...
};
return(
<View style={styles.container}>
{/* Added this scroll view to enable scrolling when list gets longer than the page */}
<ScrollView
contentContainerStyle={{
flexGrow: 1
}}
keyboardShouldPersistTaps='handled'
>
{/* Today's Tasks */}
<View style={styles.tasksWrapper}>
<Text style={styles.sectionTitle}>Today's tasks</Text>
<View style={styles.items}>
{/* This is where the tasks will go! */}
{
items.map((item, index) => {
return (
<TouchableOpacity key={index} onPress={() => completeTask(index)}>
<Text>Recipe {item.itemName} Description: {item.description}</Text>
</TouchableOpacity>
)
})
}
</View>
</View>
</ScrollView>
{/* Write a task */}
{/* Uses a keyboard avoiding view which ensures the keyboard does not cover the items on screen */}
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={styles.writeTaskWrapper}
>
<View style={{flexDirection: 'column', flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<TextInput style={styles.input} placeholder={'Write a name'} value={recipeName} onChangeText={(text) => setRecipeName(text)} />
<TextInput style={styles.input} placeholder={'Write a date'} value={descriptionItem} onChange={(text) => setDescriptionItem(text)} />
</View>
<TouchableOpacity onPress={() => handleAddButtonClick()}>
<View style={styles.addWrapper}>
<Text style={styles.addText}>+</Text>
</View>
</TouchableOpacity>
</KeyboardAvoidingView>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#E8EAED',
},
tasksWrapper: {
paddingTop: 80,
paddingHorizontal: 20,
},
sectionTitle: {
fontSize: 24,
fontWeight: 'bold'
},
items: {
marginTop: 30,
},
writeTaskWrapper: {
position: 'absolute',
bottom: 60,
width: '100%',
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center'
},
input: {
paddingVertical: 15,
paddingHorizontal: 15,
backgroundColor: '#FFF',
borderRadius: 60,
borderColor: '#C0C0C0',
borderWidth: 1,
width: 250,
},
addWrapper: {
width: 60,
height: 60,
backgroundColor: '#FFF',
borderRadius: 60,
justifyContent: 'center',
alignItems: 'center',
borderColor: '#C0C0C0',
borderWidth: 1,
},
addText: {},
});
When iterating over items in your map() function here:
items.map((item, index) => {
return (
<TouchableOpacity
key={index}
onPress={() => completeTask(index)}
>
<Text>
Recipe {item.itemName} Description: {item.description}
</Text>
</TouchableOpacity>
);
})
You are not using the correct state value. Instead of item.description, it should be item.descriptionItem.
I also advise you to move onChange events to separate methods and set the state inside them, do not use anonymous functions. So, for example, for your description, it would be something like this:
const handleDescription = (e) => {
setDescriptionItem(e.target.value);
};
And in your JSX:
<TextInput
style={styles.input}
placeholder={"Write a date"}
value={descriptionItem}
onChange={handleDescription}
/>
UPDATE:
I recreated your code in sandbox, with minor changes:
commented out the import of Task component (since I don't know what that component does)
disabled onPress event handler as I don't have access to completeTask function
changed the onChangeText for recipeName to onChange
extracted both onChange events to separated methods.
fixed initial state for items; it also had description , instead of descriptionItem
Please, feel free to check it out.
Begginer developer react native.
im dealing with design pattern issue ,
i have multiple TouchableOpacity's in the same component (i have to keep it that way).
for each one i have onPress function thats changs there background and reverse .
the problom is that the function dependent on State current statment and when i click on one of them evreyone is changing .
function Grocery({ navigation }) {
const [isPressed, setIsPressed] = useState(0);
const onPress = () => setIsPressed(!isPressed);
return (
<ScrollView>
<Button title="home" onPress={() => {FindMatch(GetIngridients());navigation.navigate("MatchedRecipiesScreen");}}>press</Button>
<View style={styles.container}>
<TouchableOpacity style={styles.button} onPress={() => {AddToPanetry("pasta");onPress();}} >
<View style={isPressed && styles.pressedButtonStyle} />
<Image style={styles.imageright} source={require('../assets/Pastaa.jpg')} />
<Text> pasta</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => {AddToPanetry("eggs");onPress();}} >
<View style={isPressed && styles.pressedButtonStyle} />
<Image style={styles.imageleft} source={require('../assets/eggs.jpg')} />
<Text>eggs</Text>
</TouchableOpacity>
const styles = StyleSheet.create({
container: {
flexDirection: "row",
flexWrap: "wrap",
padding: 50,
flexWrap: 'wrap',
justifyContent: 'space-between',
}
,
imageleft: {
borderRadius:100,
borderWidth:2,
borderColor:'black',
height: 120,
width: 150,
borderRadius: 80,
padding:25
},
button: {
alignItems: "center",
},
tinyLogo: {
width: 50,
height: 50,
},
pressedButtonStyle: {
position:"absolute",
width:150,
height:121,
backgroundColor:'black',
opacity:0.6,
zIndex:100,
borderRadius:80
},
imageright: {
borderRadius:100,
borderWidth:2,
borderColor:'black',
height: 120,
width: 150,
borderRadius: 80,
padding:25
}
});
Setup an state array
const [isPressed, setIsPressed ] = useState([true, false, false, false, false]);
Here is an sample
You can try something like this, too:
const [isPressed, setIsPressed ] = React.useState('first');
...
<TouchableOpacity style={styles.button} onPress={() => setIsPressed('first')>
<View style={isPressed === 'first' ? styles.pressedButtonStyle : null} />
</TouchableOpacity>
...
<TouchableOpacity style={styles.button} onPress={() => setIsPressed('second')>
<View style={isPressed === 'second' ? styles.pressedButtonStyle : null} />
</TouchableOpacity>
I have fetched the data successfully and also i am routing that data also. now i am trying to update that touchableOpacity TextInput field back to firestore. the process i have done is correct or i am doing some thing messy please have a look at that it would be beneficial for me. the routing process {route.params.item.MOBILE_NO} as you can see. Thankyou
const ItemDetails = ({ route }) => {
const [phone, setPhone] = useState("")
const UpdateNumber = (MOBILE_NO, updated) => {
db.collection("Data").
doc(MOBILE_NO.id)
set(MOBILE_NO).then(() => updated(MOBILE_NO)).
catch((error) => console.log(error))
}
return (
<View style={{ flex: 1, paddingTop: 253, paddingLeft: 5 }}>
<Text style={{ fontSize: 25 }}> Mobile : </Text>
</View>
<TouchableHighlight style={{ alignItems: 'flex-start', paddingLeft: 110, paddingTop:7 }}>
<TextInput placeholder = "Mobile number"
value = {phone}
onChangeText = {text => setPhone(text)}
style={{ fontSize: 25, backgroundColor:'#f0f8ff',
borderRadius:10, width:'70%',height:30, }}>
<Text style={{ fontSize: 25, }}>{route.params.item.MOBILE_NO}
</Text>
</TextInput>
</TouchableHighlight>
<Button onPress = {UpdateNumber()} title = "Update"/>
</View>
)
}
I have a touchableopacity on each card where I want to setstate of expand to true, but I want to do it according to the id, so that state of only one changes, any idea how to do it using map()?
My code:
import React, {useState, useEffect} from 'react';
import {
SafeAreaView,
Text,
Image,
ScrollView,
TouchableOpacity,
View,
} from 'react-native';
import axios from 'axios';
import {ROOT} from '../../../../ApiUrl';
import Icon from 'react-native-vector-icons/FontAwesome';
export default function VaccinationListScreen(props) {
const [expand, setExpand] = useState(false);
const [data, setData] = useState('');
let id = props.route.params.id;
const getData = () => {
let url = `some url`;
console.log('bbb');
axios
.get(url)
.then(function (res) {
console.log(res.data.content);
setData(res.data.content);
})
.catch(function (err) {
console.log(err);
});
};
useEffect(() => {
getData();
}, []);
return (
<SafeAreaView>
<ScrollView>
<TouchableOpacity style={{padding: 10}} onPress={()=>setExpand(true)}>
{data != undefined &&
data != null &&
data.map((item) => {
return (
<View
style={{
padding: 10,
backgroundColor: '#fff',
elevation: 3,
margin: '2%',
borderRadius: 5,
}}
key={item.id}>
<View style={{alignItems: 'flex-end'}}>
<Text style={{color: 'grey', fontSize: 12}}>
{item.display_date}
</Text>
</View>
<View style={{flexDirection: 'row'}}>
<View>
<Image
source={require('../../assets/atbirth.jpg')}
style={{height: 40, width: 50}}
resizeMode="contain"
/>
</View>
<View style={{flex: 1}}>
<View style={{flexDirection: 'row', flex: 1}}>
<Text
key={item.id}
style={{
fontFamily: 'Roboto',
fontSize: 18,
fontWeight: 'bold',
}}>
{item.name}
</Text>
</View>
<View style={{flexDirection: 'row', width: '30%'}}>
{item.vaccine_list.map((i) => {
return (
<View style={{flexDirection: 'row'}}>
<Text
numberOfLines={1}
ellipsizeMode="tail"
style={{fontFamily: 'Roboto', fontSize: 15}}>
{i.name},
</Text>
</View>
);
})}
</View>
</View>
</View>
<View style={{alignItems: 'flex-end', marginTop: '1%'}}>
<View style={{flexDirection: 'row'}}>
<Text
style={{
color: 'red',
fontSize: 14,
fontWeight: 'bold',
}}>
{item.child_vacc_status.text}
</Text>
<Icon
name="chevron-up"
color="red"
size={12}
style={{marginTop: '1%', marginLeft: '1%'}}
/>
</View>
</View>
</View>
);
})}
</TouchableOpacity>
</ScrollView>
</SafeAreaView>
);
}
Any suggestions would be great, do let mw know if anything else is required for better understanding
As i review your code <TouchableOpacity> wraps all of your cards at once not on each card set. If you implement your code that way if it's not impossible it will be difficult for you to reference each cards id and set the state of expand to true according to cards id.
My suggestion is to include <TouchableOpacity> to map() function nest so that it will be easy to reference each cards function.
I reproduce this specific problem and implement a solution in which I was able to set the state of expand to true according to each cards id.
You may click the sandbox link to see a demonstration.
https://codesandbox.io/s/accordingtoid-4px1w
Code in Sandbox:
import React, { useState, useEffect } from "react";
import {
SafeAreaView,
Text,
Image,
TouchableOpacity,
View
} from "react-native";
// import axios from 'axios';
// import {ROOT} from '../../../../ApiUrl';
// import Icon from "react-native-vector-icons/FontAwesome";
export default function VaccinationListScreen(props) {
const [expand, setExpand] = useState({});
const [data, setData] = useState([]);
// let id = props.route.params.id;
// const getData = () => {
// let url = `some url`;
// console.log('bbb');
// axios
// .get(url)
// .then(function (res) {
// console.log(res.data.content);
// setData(res.data.content);
// })
// .catch(function (err) {
// console.log(err);
// });
// };
// useEffect(() => {
// getData();
// }, []);
useEffect(() => {
// In order to simulate and reproduce the problem
// Assume that these are the data that you fetch from an API
const dataContent = [
{
id: 1,
name: "At Birth",
display_date: "02 May - 08 May 16",
vaccine_list: [
{ name: "BCG" },
{ name: "Hepatitis B" },
{ name: "OPV 0" }
],
child_vacc_status: { text: "Missed" }
},
{
id: 2,
name: "At 6 Weeks",
display_date: "02 May - 08 May 16",
vaccine_list: [
{ name: "IPV" },
{ name: "PCV" },
{ name: "Hepatitis b" },
{ name: "DTP" },
{ name: "HiB" },
{ name: "Rotavirus" }
],
child_vacc_status: { text: "Missed" }
}
];
setData(dataContent);
}, []);
function handleOnPress(id) {
setExpand((prev) => {
let toggleId;
if (prev[id]) {
toggleId = { [id]: false };
} else {
toggleId = { [id]: true };
}
return { ...toggleId };
});
}
useEffect(() => {
console.log(expand); // check console to see the value
}, [expand]);
return (
<SafeAreaView>
{data !== undefined &&
data !== null &&
data.map((item) => {
return (
<TouchableOpacity
key={item.id}
style={{
padding: 10
}}
onPress={() => handleOnPress(item.id)}
>
<View
style={{
padding: 10,
backgroundColor: expand[item.id] ? "lightgrey" : "#fff",
elevation: 3,
margin: "2%",
borderRadius: 5
}}
>
<View style={{ alignItems: "flex-end" }}>
<Text style={{ color: "grey", fontSize: 12 }}>
{item.display_date}
</Text>
</View>
<View style={{ flexDirection: "row" }}>
<View>
<Image
// source={require('../../assets/atbirth.jpg')}
style={{ height: 40, width: 50 }}
resizeMode="contain"
/>
</View>
<View style={{ flex: 1 }}>
<View style={{ flexDirection: "row", flex: 1 }}>
<Text
key={item.id}
style={{
fontFamily: "Roboto",
fontSize: 18,
fontWeight: "bold"
}}
>
{item.name}
</Text>
</View>
<View style={{ flexDirection: "row", width: "30%" }}>
{item.vaccine_list.map((item, i) => {
return (
<View key={i} style={{ flexDirection: "row" }}>
<Text
numberOfLines={1}
ellipsizeMode="tail"
style={{ fontFamily: "Roboto", fontSize: 15 }}
>
{item.name},
</Text>
</View>
);
})}
</View>
</View>
</View>
<View style={{ alignItems: "flex-end", marginTop: "1%" }}>
<View style={{ flexDirection: "row" }}>
<Text
style={{
color: "red",
fontSize: 14,
fontWeight: "bold"
}}
>
{item.child_vacc_status.text}
</Text>
</View>
</View>
</View>
</TouchableOpacity>
);
})}
</SafeAreaView>
);
}
I haven't tested the code to work correctly, but you could try something similar. You could create a separate component for the items and set a status for each of them.
export default function VaccinationListScreen(props) {
const [expand, setExpand] = useState(false);
const [data, setData] = useState("");
const VaccinationListItem = (item) => {
const [expand, setExpand] = useState(false);
return (
<TouchableOpacity style={{ padding: 10 }} onPress={() => setExpand(true)}>
<View
style={{
padding: 10,
backgroundColor: "#fff",
elevation: 3,
margin: "2%",
borderRadius: 5,
}}
key={item.id}
>
<View style={{ alignItems: "flex-end" }}>
<Text style={{ color: "grey", fontSize: 12 }}>
{item.display_date}
</Text>
</View>
<View style={{ flexDirection: "row" }}>
<View>
<Image
source={require("../../assets/atbirth.jpg")}
style={{ height: 40, width: 50 }}
resizeMode="contain"
/>
</View>
<View style={{ flex: 1 }}>
<View style={{ flexDirection: "row", flex: 1 }}>
<Text
key={item.id}
style={{
fontFamily: "Roboto",
fontSize: 18,
fontWeight: "bold",
}}
>
{item.name}
</Text>
</View>
<View style={{ flexDirection: "row", width: "30%" }}>
{item.vaccine_list.map((i) => {
return (
<View style={{ flexDirection: "row" }}>
<Text
numberOfLines={1}
ellipsizeMode="tail"
style={{ fontFamily: "Roboto", fontSize: 15 }}
>
{i.name},
</Text>
</View>
);
})}
</View>
</View>
</View>
<View style={{ alignItems: "flex-end", marginTop: "1%" }}>
<View style={{ flexDirection: "row" }}>
<Text
style={{
color: "red",
fontSize: 14,
fontWeight: "bold",
}}
>
{item.child_vacc_status.text}
</Text>
<Icon
name="chevron-up"
color="red"
size={12}
style={{ marginTop: "1%", marginLeft: "1%" }}
/>
</View>
</View>
</View>
</TouchableOpacity>
);
};
return (
<SafeAreaView>
<ScrollView>
{data != undefined &&
data != null &&
data.map((item) => {
VaccinationListItem(item);
})}
</ScrollView>
</SafeAreaView>
);
}
Generally if you want to toggle any single element you should store its id in expand (instead of a boolean), and simply check when rendering the array if any specific element's id matches, i.e. element.id === expand. When any new element is touched, pop its id in there, if the id is already there, set to null to collapse.
export default function VaccinationListScreen(props) {
const [expandId, setExpandId] = useState(null); // <-- stores null or id, initially null
...
// Create curried handler to set/toggle expand id
const expandHandler = (id) => () =>
setExpandId((oldId) => (oldId === id ? null : id));
return (
<SafeAreaView>
<ScrollView>
{data &&
data.map((item) => {
return (
<View
...
key={item.id}
>
<TouchableOpacity
style={{ padding: 10 }}
onPress={expandHandler(item.id)} // <-- attach callback and pass id
>
...
</TouchableOpacity>
{item.id === expandId && ( // <-- check if id match expand id
<ExpandComponent />
)}
</View>
);
})}
</ScrollView>
</SafeAreaView>
);
}
I'm trying to move the selection text to the far right on this Detail component. I've tried justifyContent on the View with the sideBySide style and alignSelf on the selectionStyle. I've tried various combinations but am not able to move the selection text to the far right, up until where the arrow might be. I feel like there's something simple I'm missing.
Here's what it looks like (Selection is not to the far right):
Thanks for any help!
const Detail = ({ onPress, selection, text, info }) => {
const wrapper = {
flex: 1,
paddingTop: 20,
paddingLeft: 16,
};
const sideBySide = {
flexDirection: 'row',
};
const textStyle = {
flex: 1.3,
fontWeight: '500',
fontSize: 12,
paddingRight: 5,
};
const selectionStyle = {
flex: 1,
color: 'blue',
fontWeight: '500',
fontSize: 12,
};
const infoWrapper = {
paddingTop: 8,
paddingBottom: 20,
};
return (
<ViewWrapper onPress={onPress} noArrow={true}>
<View style={wrapper}>
<View style={sideBySide}>
<Text style={textStyle}>
{text}
</Text>
<Text style={selectionStyle}>
{selection}
</Text>
</View>
<View style={infoWrapper}>
<Text style={subHeader}>
{info}
</Text>
</View>
</View>
</ViewWrapper>
);
};
const iconView = { position: 'absolute', right: 16 };
const icon = { color: 'gray', fontSize: 14 };
const ViewWrapper = ({ onPress, children, noArrow, style }) => {
const view = {
flexDirection: 'row',
alignItems: 'center',
paddingRight: Platform.OS === 'ios' && !noArrow ? 35 : 16,
backgroundColor: 'white',
};
return (
<TouchableHighlight underlayColor="#eee" onPress={onPress}>
<View style={[view, style]}>
{children}
{Platform.OS === 'ios' &&
!noArrow && (
<View style={iconView}>
<Icon name="arrow" style={icon} />
</View>
)}
</View>
</TouchableHighlight>
);
};
Remove flex: 1 from selectionStyle