Toggling classes to a specific item in a .map() function - javascript

I'm working with React Native, and I have set up a button where you can toggle the click and it'll add classes to it. However, if I have an array of items, its adding the class to all the items. I just want to add the class to the clicked button.
const Tags = props => {
const [selectTag, setSelectTag] = useState(false);
const tags = ['apples', 'oranges', 'lemon', 'watermelon', 'green peas', 'grapes'];
// toggles the click
const handleSelectTags = clicked => {
setSelectTag(clicked);
};
return (
<>
<View>
{tags.map((item, index) => (
<TouchableOpacity
onPress={() => handleSelectTags(!selectTag)}
key={index}
style={
// when selectTag is true, adds styles.selected
selectTag
? [styles.tags, styles.selected]
: [styles.tags, styles.notSelected]
}>
<Text>{item}</Text>
</TouchableOpacity>
))}
</View>
</>
);
};
const styles = StyleSheet.create({
tags: {
paddingVertical: 3,
paddingHorizontal: 9,
borderWidth: 1,
marginRight: 8,
marginBottom: 8,
borderRadius: 8,
},
selected: {
borderColor: 'green',
},
notSelected: {
borderColor: '#ccc',
},
});
export default Tags;
Not sure if what I have is the best way to do this, wondering if there's a more elegant way?

Have each TouchableOpacity manage it's own state by making it into a component. That way every time you click an item it's just updating itself :) This will allow you to have multiple clicked at the same time
const CustomComponent = props => {
let [selected, setSelected] = useState(false)
return (
<TouchableOpacity
onPress={() => setSelected(!selected)}
style={
selected
? [styles.tags, styles.selected]
: [styles.tags, styles.notSelected]
}>
<Text>{props.item}</Text>
</TouchableOpacity>
)
}
And then render this component in your map
{
tags.map((item, index) => (
<CustomComponent
key={index}
item={item}
/>
))
}

Try this if it correct
const [selectTag, setSelectTag] = useState('');
const tags = ['apples', 'oranges', 'lemon', 'watermelon', 'green peas', 'grapes'];
// toggles the click
const handleSelectTags = tag => {
setSelectTag(tag);
};
return (
<>
<View>
{tags.map((item, index) => (
<TouchableOpacity
onPress={() => handleSelectTags(item)}
key={index}
style={
// when selectTag is true, adds styles.selected
selectTag == item
? [styles.tags, styles.selected]
: [styles.tags, styles.notSelected]
}>
<Text>{item}</Text>
</TouchableOpacity>
))}
</View>
</>
);

I propose so give each button its own state.
CODE:
// each button has his own entry
const [selectTag, setSelectTag] = useState([false, false, false, false, false, false]);
const tags = ['apples', 'oranges', 'lemon', 'watermelon', 'green peas', 'grapes'];
// toggles the click
const handleSelectTags = (index) => {
var tmp = [ ...selectTag ]
tmp[index] = !selectTag[index]; // update
setSelectTag(tmp);
}
return
<>
<View>
{tags.map((item, index) => (
<TouchableOpacity
onPress={() => handleSelectTags(index)}
key={index}
style={
// update here to index of selectTag
selectTag[index]
? [styles.tags, styles.selected]
: [styles.tags, styles.notSelected]
}>
<Text>{item}</Text>
</TouchableOpacity>
))}
</View>
</>
OUTPUT
DEMO:
https://snack.expo.io/rJdZr!AHL

Related

React native single selectable components

I am trying to achieve a simple single selectable item, as shown in the image below.
Right now, I have created an array of my data items and using .map to render the components because there are only 3-4 items max, now I want to select only a single item and change the color on the basis, and if I select any other item, it should unselect all the other items but not the current single selected item/component. I tried to do this but I am able to select all of them, obviously. Below is the code:
const items = [
{
id: 1,
iconName: 'male',
title: 'Male',
name: 'male',
},
{
id: 2,
iconName: 'female',
title: 'Female',
name: 'female',
},
{
id: 3,
iconName: 'transgender',
title: 'Others',
name: 'others',
},
];
const Component = ({dispatch, iconName, title, name}) => {
const [isSelected, setIsSelected] = useState(false);
return (
<TouchableOpacity
activeOpacity={0.6}
style={
isSelected
? [styles.selectable, {backgroundColor: 'green'}]
: [styles.selectable, {backgroundColor: COLORS.PINK}]
}
onPress={() => {
setIsSelected(!isSelected);
}}>
<View style={styles.row}>
<Ionicon name={iconName} size={36} color="#fff" />
<Text>{title}</Text>
</View>
</TouchableOpacity>
);
};
const Gender = () => {
return (
<View>
<View>
<Text>Your Gender</Text>
<View>
{items.map(item => (
<Component
key={item.id}
title={item.title}
iconName={item.iconName}
/>
))}
</View>
</View>
</View>
);
};
All though I could solve this by using separate states for each button, so whenever one is selected/pressed, the other states should become false. But then I would have to render individual component without using the .map method which I find inefficient. Can someone provide any solution based on my current approach to this problem?
Thank you!
Consider moving isSelected to the parent component, and instead of storing a booolean, store the selected item id. Pass the itemId, selectedId, setSelectedId (as a callback) to the child components and change the style check to:
style={
itemId === selectedId
? [styles.selectable, {backgroundColor: 'green'}]
: [styles.selectable, {backgroundColor: COLORS.PINK}]
}
onPress={() => {
setSelectedId(itemId);
}}>
Now you can get rid of keeping track whether the item is selected in the component, and only worry about it in the context of the parent (as it should be).
const Gender = () => {
const [selectedId, setSelectedId] = useState(false);
return (
<View>
<View>
<Text>Your Gender</Text>
<View>
{items.map(item => (
<Component
key={item.id}
itemId={item.id}
selectedId={selectedId}
setSelectedId={setSelectedId}
title={item.title}
iconName={item.iconName}
/>
))}
</View>
</View>
</View>
);
};

react native: Displays a single table with the data

In my code I expect to get only one table with the data but what happens is that I get a lot of tables with the same data and it is not clear to me why.
Lots of tables are accepted and I should get only one table.
I would be happy to help with this
export const ActionModal = (props: any) => {
const { pointsData } = useGetPoints();
const [tableHead] = useState(['אזור', 'אתר', 'נ`ק איסוף', 'מיקום']);
const [tableData, setTableData] = useState([]);
const arrangeData = () => {
let rows: any[] = [];
pointsData.forEach(e => {
let row = [e.area, e.site, e.gatheringPoint, e.location];
rows.push(row);
});
setTableData(rows);
}
useEffect(() => {
arrangeData();
}, []);
const renderItem = ({ item }: any) => (
<View style={styles.item}>
<View style={styles.tableView}>
<Table borderStyle={{ borderWidth: 2, borderColor: '#c8e1ff' }}>
<Row data={tableHead} style={styles.head} textStyle={styles.text} />
<Rows data={tableData} textStyle={styles.text} />
</Table>
</View>
<Text style={{ fontSize: 18 }}>{item.area}, {item.site}, {item.gatheringPoint}, {item.location}</Text>
</View>
)
return (
<Modal
animationType={'slide'}
transparent={false}
visible={props.actionModalVisible}
onRequestClose={() => {
console.log('Modal has been closed.');
}}>
<View style={styles.modal}>
{pointsData.length ?
<FlatList
data={pointsData}
renderItem={renderItem}
keyExtractor={item => item.gatheringID}
/> :
<ActivityIndicator size="large" />}
</View>
<Button
title="סגור"
onPress={props.onClose}
/>
</Modal>
);
};
Table are rendered inside a item of flatlist
the function renderItem will repeate like a number of row of you array , this why you get lot of table ..
to fix this you need to pull out of flatlist like this to rendering table once time :
export const ActionModal = (props: any) => {
const { pointsData } = useGetPoints();
const [tableHead] = useState(['אזור', 'אתר', 'נ`ק איסוף', 'מיקום']);
const [tableData, setTableData] = useState([]);
const arrangeData = () => {
let rows: any[] = [];
pointsData.forEach(e => {
let row = [e.area, e.site, e.gatheringPoint, e.location];
rows.push(row);
});
setTableData(rows);
}
useEffect(() => {
arrangeData();
}, []);
const renderItem = ({ item }: any) => (
<Text style={{ fontSize: 18 }}>{item.area}, {item.site}, {item.gatheringPoint}, {item.location}</Text>
)
return (
<Modal
animationType={'slide'}
transparent={false}
visible={props.actionModalVisible}
onRequestClose={() => {
console.log('Modal has been closed.');
}}>
<View style={styles.modal}>
{pointsData.length ?
<View style={styles.item}>
<View style={styles.tableView}>
<Table borderStyle={{ borderWidth: 2, borderColor: '#c8e1ff' }}>
<Row data={tableHead} style={styles.head} textStyle={styles.text} />
<Rows data={tableData} textStyle={styles.text} />
</Table>
</View>
<FlatList
data={pointsData}
renderItem={renderItem}
keyExtractor={item => item.gatheringID}
/>
</View>:
<ActivityIndicator size="large" />}
</View>
<Button
title="סגור"
onPress={props.onClose}
/>
</Modal>
);
};

New to react native, Buttons don't seem to work individually

I'm trying to get each button to activate and "switch-on" when pressed, and I've used some documentation to help me. However, now it is not switching on.
Code Adds Switches in a FlatList
The Data should look like this:
https://imgur.com/a/761PSjre
Also, feel free to judge my code to the darkest depths of hell. I'm trying to improve my code.
import React from 'react'
import {StyleSheet, View,Text, Switch, Button, Alert, ScrollView, FlatList, SafeAreaView} from 'react-native'
export default () => {
const DATA = [
{
index: 1,
title: 'Toggle Night Mode',
},
{
index: 2,
title: 'Remind me to take a break',
},
{
index: 3,
title: "Remind me when it's bedtime",
},
];
const [enabledSwitches, setEnabledSwitches] = React.useState(DATA.length);
const toggleSwitch = () => setEnabledSwitches(previousState => !previousState);
function Item({title, index}) {
return (
<View>
<Text style={styles.text}> {title} </Text>
<Switch
trackColor={{ false: "#767577", true: "#81b0ff" }}
thumbColor="#f5dd4b"
ios_backgroundColor="#3e3e3e"
value={enabledSwitches[index]}
onValueChange={() => toggleSwitch(switches => {
switches[index] = !switches[index];
return switches;
})}
/>
</View>
)
}
function Header(){
return(
<View style = {styles.header}>
<Text style={styles.headertext}>Settings</Text>
</View>
)
}
return (
<>
<View style = {styles.container}>
<FlatList
data = {DATA}
keyExtractor = {item => item.id}
renderItem = {({ item, index }) => <Item title={item.title} index={index} /> }
ListHeaderComponent = {Header()}
/>
</View>
<View>
<Button
title = "Clear Search History"
color = "#6fb6f0"
onPress = {() => Alert.alert('Food History Has Been Cleared!')}
/>
</View>
<View>
<Button
title = "Logout"
color = "#6fb6f0"
onPress = {() => Alert.alert('Successfully Logged Out!')}
/>
</View>
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
text: {
fontSize: 20,
fontWeight: "300"
},
headertext: {
fontSize: 30,
fontWeight: "300"
},
header:{
flex:1,
justifyContent: 'center',
alignItems: 'center',
padding: 10,
backgroundColor: '#f5f5f5'
}
})
Try this way
import React from 'react'
import {StyleSheet, View,Text, Switch, Button, Alert, ScrollView, FlatList, SafeAreaView} from 'react-native'
export default () => {
// use data set in default state
const [data, setData] = React.useState([ {index: 1,title: 'Toggle Night Mode'},...]);
function toggleSwitch(value, index){
const newData = [...data];
const newData[index].isEnable = value;
setData(newData);
}
function Item({item, index}) {
return (
<View>
<Text style={styles.text}> {item.title} </Text> // use `title` here like this
<Switch
.....
value={item.isEnable || false} // change here
onValueChange={(value) => toggleSwitch(value, index) } // change here
/>
</View>
)
}
return (
<>
<View style = {styles.container}>
<FlatList
data = {data}
keyExtractor = {item => item.id}
renderItem = {({ item, index }) => <Item item={item} index={index} /> } // send `item` as prop
/>
</View>
</>
);
}

How to send value from one tab to another tab in react native

Here in my code I am making tree tabs , on first tabe there are two input fields and buttons.
Now after entering the value in input and on button click i have to send vale to oter tabs.
Like in in name field I am entering name "Abhi" and on button click this Abhi should reflect on Tab 2.
Same like in Animal field , this Animal should reflect on third tab .
Please help
import * as React from 'react';
import { View, StyleSheet, Dimensions,Text,TextInput,TouchableOpacity } from 'react-native';
import { TabView, SceneMap } from 'react-native-tab-view';
const FirstRoute = () => (
<View style={[styles.scene, { backgroundColor: '#FFFFFF' }]} >
<View style={{}}>
<Text style={{margin:15}}>Name </Text>
<TextInput style={styles.input}
underlineColorAndroid = "transparent"
placeholder = "Name"
placeholderTextColor = "#9a73ef"
autoCapitalize = "none"
onChangeText={text => onChangeText(text)}
/>
<TouchableOpacity
style = {styles.submitButton}
onPress = {
() => this.Name()
}>
<Text style = {styles.submitButtonText}> Submit </Text>
</TouchableOpacity>
</View>
<View style={{}}>
<Text style={{margin:15}}> Favorite Animal </Text>
<TextInput style={styles.input}
underlineColorAndroid = "transparent"
placeholder = "Favorite Animal"
placeholderTextColor = "#9a73ef"
autoCapitalize = "none"
onChangeText={text => onChangeText(text)}
/>
<TouchableOpacity
style = {styles.submitButton}
onPress = {
() => this.Animal()
}>
<Text style = {styles.submitButtonText}> Submit </Text>
</TouchableOpacity>
</View>
</View>
);
const SecondRoute = () => (
<View style={[styles.scene, { backgroundColor: '#FFFFFF' }]} >
<Text> {'Name' }</Text>
</View>
);
const ThirdRoute = () => (
<View style={[styles.scene, { backgroundColor: '#FFFFFF' }]} >
<Text> {"Favorite Animal "}</Text>
</View>
);
const initialLayout = { width: Dimensions.get('window').width };
export default function TabViewExample() {
const [index, setIndex] = React.useState(0);
const [routes] = React.useState([
{ key: 'first', title: 'First' },
{ key: 'second', title: 'Second' },
{ key: 'third', title: 'Third' },
]);
const renderScene = SceneMap({
first: FirstRoute,
second: SecondRoute,
third:ThirdRoute
});
return (
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={setIndex}
initialLayout={initialLayout}
/>
);
}
const styles = StyleSheet.create({
scene: {
flex: 1,
},
container: {
paddingTop: 23
},
input: {
margin: 15,
height: 40,
borderColor: '#7a42f4',
borderWidth: 1
},
submitButton: {
backgroundColor: '#65D370',
padding: 10,
margin: 15,
height: 40,
},
submitButtonText:{
color: 'white',
alignSelf:'center',
justifyContent:'center',
borderRadius:20
}
});
Shortest answer, is try to use a state. Using states and passing the state from parent to child may be your best option. Here is one way you can go about it.
1st in your TabViewExample add a useState() hook to keep the form data and change your renderScene() to a function, do not use SceneMap. Example:
...
const [name, setName] = React.useState(undefined);
const renderScene = ({ route }) => {
switch (route.key) {
case "first":
return <FirstRoute setName={setName} />;
case "second":
return <SecondRoute name={name} />;
case "third":
return <ThirdRoute />;
default:
<FirstRoute setName={setName} />;
}
};
(A) The reason for using renderScene() as function is explained with more detail on the "react-native-tab-view" documentation. In short when you need to pass props to components you should not use SceneMap() which you are using above instead turn renderScene into a function and use switch.
(B) We only passed setName to the first component because that's what we'll be using.
2nd - Make use of the props in your components. So now they'll look more or less like this:
const FirstRoute = props => (
<View style={[styles.scene, { backgroundColor: "#FFFFFF" }]}>
<View style={{}}>
<Text style={{ margin: 15 }}>Name </Text>
<TextInput
style={styles.input}
underlineColorAndroid="transparent"
placeholder="Name"
placeholderTextColor="#9a73ef"
autoCapitalize="none"
onChangeText={text => props.setName(text)}
/>
...
And for the SecondRoute :
const SecondRoute = props => (
<View style={[styles.scene, { backgroundColor: "#FFFFFF" }]}>
<Text> {props.name}</Text>
</View>
);
So now when you change the first Input in FirstRoute, the name state will automatically be updated, so when you go/swipe to page 2, you should see whatever you typed on the first TextInput on page 1.
PS: this is just a brief explanation so I just gave you the essential idea behind sharing data across tabs/components. On your code you can create cleaner form handler functions and handler functions for those buttons. I could've done it, but I'll leave that job for you as it was not part of your initial question. Hope this helps and let me know if you need a more detailed/in-depth response.
PS 2: If you use my current example don't click the buttons otherwise you'll get errors because you have no handler function, just type on the input and go to the second page to see the result.

How to pass props to FlatList with renderScene in react-native-tab-view?

I'm a newbie in React Native and trying to pass props to ListHeaderComponent in FlatList
Here's the code:
const FirstRoute = (props) => {
const _renderHeader = () => {
return(
<View>
{props.isFollowed &&
<TouchableOpacity onPress={props.onSubmit}>
<Text>You have followed this person</Text>
</TouchableOpacity> }
</View>
)
}
return(
<View style={[styles.scene, { backgroundColor: '#ff4081' }]}>
<FlatList
data={data}
keyExtractor={item => item.id}
renderItem={itemData => ( <Image source={itemData.item.id} style={{height: WIDTH/3, width: WIDTH/3}} />)}
ListHeaderComponent={_renderHeader}
/>
</View>
)
};
const SecondRoute = () => (
<View style={[styles.scene, { backgroundColor: '#673ab7' }]} />
);
const initialLayout = { width: Dimensions.get('window').width };
export default function Parent() {
const [index, setIndex] = React.useState(0);
const [routes] = useState([
{ key: 'first', title: 'First' },
{ key: 'second', title: 'Second' },
]);
const [_isFollowed, setIsFollowed] = useState(false);
const _onSubmit = () => {
...
setIsfollowed(true)
}
const renderScene = ({route}) => {
switch(route.key){
case 'first': return <FirstRoute {...props} onSubmit={_onSubmit} isFollowed={_isFollowed} />
case 'second': return <SecondRoute {...props} />
}
};
return (
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={setIndex}
initialLayout={initialLayout}
/>
);
}
But when I save it, the screen logs the error: Can't find the value of isFollowed
I think the problem is at the way I pass the props. I'm still learning it. Since when I delete the ListHeaderComponent, the FlatList still generates the list of images well. And I don't know if it has something to do with renderScene
I really don't understand why
Please help me. Thank you very much
Let me get this straight. You need to render _renderHeader dinamically based on _isFollowed state. So, you passed to the first route as props your _onSubmit function and _isFollowed state in order to get to access them at _renderHeader. Right?
As I see you actually doesn't need to do it once your _renderHeader has direct access to both _isFollowed state and _onSubmit function. Try it out as bellow:
export default function Parent() {
const [index, setIndex] = React.useState(0);
const [routes] = useState([
{ key: 'first', title: 'First' },
{ key: 'second', title: 'Second' },
]);
const [_isFollowed, setIsFollowed] = useState(false);
const initialLayout = { width: Dimensions.get('window').width };
function _onSubmit() {
setIsFollowed(true);
}
function _renderHeader() {
return (
<View>
{_isFollowed &&
<TouchableOpacity onPress={_onSubmit}>
<Text>You have followed this person</Text>
</TouchableOpacity> }
</View>
);
}
const FirstRoute = () => {
return(
<View style={[styles.scene, { backgroundColor: '#ff4081' }]}>
<FlatList
data={data}
keyExtractor={item => item.id}
renderItem={itemData => ( <Image source={itemData.item.id} style={{height: WIDTH/3, width: WIDTH/3}} />)}
ListHeaderComponent={_renderHeader}
/>
</View>
)
};
const SecondRoute = () => (
<View style={[styles.scene, { backgroundColor: '#673ab7' }]} />
);
const renderScene = ({route}) => {
switch(route.key){
case 'first': return <FirstRoute />
case 'second': return <SecondRoute />
}
};
return (
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={setIndex}
initialLayout={initialLayout}
/>
);
}
Other point I don't understand in your code and couldn't check cause I didn't try to run it was the function bellow:
function _renderHeader() {
return (
<View>
{_isFollowed &&
<TouchableOpacity onPress={_onSubmit}>
<Text>You have followed this person</Text>
</TouchableOpacity> }
</View>
);
}
If you want to render TouchableOpacity just in case _isFollowed is true you should do it using ternary operator like so:
function _renderHeader() {
return (
<View>
{_isFollowed ?
<TouchableOpacity onPress={_onSubmit}>
<Text>You have followed this person</Text>
</TouchableOpacity> }
: <></>
}
</View>
);
}

Categories

Resources