Updating state array in react native - arrays

I'm trying to keep track of a list of chosen exercises, and then render 'added' if the chosen exercise is in the list, or 'not added' if it's not in the list. I add the exercise to chosenExerciseArray when it's not already included, and remove it if it is. When I use console.log(chosenExerciseArray) it correctly displays the list, but if I use setChosenExercises(chosenExerciseArray) the state does not get updated correctly, and the chosenExerciseArray somehow stops logging the correct array as well.
I've also tried adding chosenExerciseArray as a useEffect dependency, but that causes an infinite loop. I'm really not sure what's going on here, but I believe it's an issue with my understanding of state.
EDIT: The TouchableOpacity is inside of a map, which may also be the issue, but I'm not sure how
<View style={styles.exerciseheadericongroup}>
<TouchableOpacity
onPress={() => {
if(!chosenExerciseArray.includes(exercise.id)) {
chosenExerciseArray.push(exercise.id);
} else {
for(var i=0; i<chosenExerciseArray.length; i++) {
if(chosenExerciseArray[i] === exercise.id) {
chosenExerciseArray.splice(i, 1);
}
}
}
console.log(chosenExerciseArray);
}}
>
{chosenExercises.includes(exercise.id) ? (
<Text>added</Text>
) : (
<Text>not added</Text>
)}
</TouchableOpacity>
</View>

As stated in the React documentation
state is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state and props. [link]
I wish I could explain it better, but basically it means that manipulating chosenExerciseArray with .push and then executing setChosenExerciseArray(chosenExerciseArray) messes up the state handling. You need to make a copy of chosenExerciseArray and manipulate it instead, and then use the manipulated copy to set the new state. I have created two examples for you to look at:
Example 1 uses a different logic, where added is part of the exercise object itself, such that that the chosenExerciseArray is not necessary to keep track of whether the exercise is added or not. This is also a bit more efficient, because you do not need to check if an exercise is in the chosenExerciseArray or not.
import React, { useState } from "react";
import { Pressable, StyleSheet } from "react-native";
import { Text, View } from "../components/Themed";
interface Exercise {
id: string;
name: string;
added: boolean;
}
const updateExerciseArray = (exercises: Exercise[], exercise: Exercise) => {
// Shallow copy
const updatedExercises = [...exercises];
// Manipulate the copy
return updatedExercises.map((updatedExercise) => {
if (updatedExercise.id === exercise.id) {
// Shallow copy of object in array where added is negated
return { ...updatedExercise, added: !updatedExercise.added };
}
return updatedExercise;
});
};
export default function Example1() {
const [exercises, setExercises] = useState<Exercise[]>([
{ id: "1", name: "Pushups", added: false },
{ id: "2", name: "Situps", added: false },
{ id: "3", name: "Squats", added: false },
{ id: "4", name: "Pullups", added: false },
{ id: "5", name: "Leg Raises", added: false },
{ id: "6", name: "Plank", added: false },
{ id: "7", name: "Burpees", added: false },
{ id: "8", name: "Jumping Jacks", added: false },
{ id: "9", name: "Wall Sit", added: false },
]);
return (
<View style={styles.container}>
{exercises.map((exercise) => {
return (
<Pressable
key={exercise.id}
onPress={() => {
setExercises(updateExerciseArray(exercises, exercise));
}}
style={styles.row}
>
<Text style={styles.text}>{exercise.name}</Text>
<Text style={styles.text}>
{exercise.added ? "Added" : "Not added"}
</Text>
</Pressable>
);
})}
</View>
);
}
const styles = StyleSheet.create({
container: {
paddingHorizontal: 50,
flex: 1,
backgroundColor: "#fff",
justifyContent: "center",
},
row: {
marginVertical: 10,
flexDirection: "row",
justifyContent: "space-between",
},
text: {
fontSize: 20,
},
});
Example 2 uses a solution that fits your current implementation.
import React, { useState } from "react";
import { Pressable, StyleSheet } from "react-native";
import { Text, View } from "../components/Themed";
interface Exercise {
id: string;
name: string;
}
const updateExerciseArray = (
chosenExerciseArray: string[],
exerciseToChange: Exercise
) => {
// Shallow copy
const updatedChosenExerciseArray = [...chosenExerciseArray];
if (updatedChosenExerciseArray.includes(exerciseToChange.id)) {
// Remove the exercise from the array
return updatedChosenExerciseArray.filter(
(exerciseId) => exerciseId !== exerciseToChange.id
);
} else {
// Append the exercise to the array
return [...updatedChosenExerciseArray, exerciseToChange.id];
}
};
export default function Example2() {
const [exercises] = useState<Exercise[]>([
{ id: "1", name: "Pushups" },
{ id: "2", name: "Situps" },
{ id: "3", name: "Squats" },
{ id: "4", name: "Pullups" },
{ id: "5", name: "Leg Raises" },
{ id: "6", name: "Plank" },
{ id: "7", name: "Burpees" },
{ id: "8", name: "Jumping Jacks" },
{ id: "9", name: "Wall Sit" },
]);
const [chosenExerciseArray, setChosenExerciseArray] = useState<string[]>([
"1",
"2",
]);
return (
<View style={styles.container}>
{exercises.map((exercise) => {
return (
<Pressable
key={exercise.id}
onPress={() => {
setChosenExerciseArray(
updateExerciseArray(chosenExerciseArray, exercise)
);
}}
style={styles.row}
>
<Text style={styles.text}>{exercise.name}</Text>
<Text style={styles.text}>
{chosenExerciseArray.includes(exercise.id)
? "Added"
: "Not added"}
</Text>
</Pressable>
);
})}
</View>
);
}
const styles = StyleSheet.create({
container: {
paddingHorizontal: 50,
flex: 1,
backgroundColor: "#fff",
justifyContent: "center",
},
row: {
paddingVertical: 10,
flexDirection: "row",
justifyContent: "space-between",
},
text: {
fontSize: 20,
},
});

Related

How to display different data if a switch is toggled in React-Native

I'm trying to make a donut chart that shows different values. But if the switch on the screen it toggled, I want it to display a different set of values.
All the data is currently stored in an array I called data.
Donut chart package I'm using
I tried solving this problem with an if-statement to check if the toggle is enabled and changed the values of the data array accordingly. However, when I try this, I get the error "data" is read-only when I switch the toggle on.
See relevant code:
import { React, useState } from "react";
import { Switch } from "react-native";
import { DonutChart } from "react-native-circular-chart";
const ResultScreen = ({ navigation }) => {
const [isEnabled, setIsEnabled] = useState(false);
const data = [
{ name: "Privacy", value: 30, color: "blue" },
{ name: "Integriteit", value: 20, color: "green" },
{ name: "Collegialiteit", value: 15, color: "pink" },
{ name: "Slaap", value: 35, color: "red" },
];
const toggleSwitch = () => {
if (isEnabled) {
data = [
{ name: "Privacy", value: 30, color: "blue" },
{ name: "Integriteit", value: 20, color: "green" },
{ name: "Collegialiteit", value: 15, color: "pink" },
{ name: "Slaap", value: 35, color: "red" },
];
} else {
data = [
{ name: "Honor", value: 50, color: "blue" },
{ name: "Usage 2", value: 10, color: "green" },
{ name: "Usage 3", value: 10, color: "pink" },
{ name: "Usage 4", value: 30, color: "red" },
];
}
setIsEnabled((previousState) => !previousState);
};
return (
<DonutChart
data={data}
strokeWidth={15}
radius={100}
containerWidth={500}
containerHeight={500}
type="butt"
startAngle={0}
endAngle={360}
animationType="slide"
/>
);
};
export default ResultScreen;
Feel free to reach out if you need any more context!
You have a few options here, the quickest one given your code would be to add ternary check in your data prop of the chart, so when you change the toggle state, it will display different data. It would look like this
const enabledData = [
{ name: "Privacy", value: 30, color: "blue" },
{ name: "Integriteit", value: 20, color: "green" },
{ name: "Collegialiteit", value: 15, color: "pink" },
{ name: "Slaap", value: 35, color: "red" },
];
const disabledData = [
{ name: "Honor", value: 50, color: "blue" },
{ name: "Usage 2", value: 10, color: "green" },
{ name: "Usage 3", value: 10, color: "pink" },
{ name: "Usage 4", value: 30, color: "red" },
];
const toggleSwitch = () => {
setIsEnabled(!isEnabled);
};
return (
<DonutChart
data={isEnabled ? enabledData : disabledData}
strokeWidth={15}
radius={100}
containerWidth={500}
containerHeight={500}
type="butt"
startAngle={0}
endAngle={360}
animationType="slide"
/>
);
This should do the job for you, however I think you should look into optimising this logic/component if you are building something scalable.
Your data is a const try declaring it using let.
eg let data = []

Get data from an array to create dynamically <Text> component react-native

I got array:
const DATA_Section = [
{
id: 0,
text: "Some text1",
},
{
id: 1,
text: "Some text2",
},
{
id: 2,
text: "Some text 3",
},
];
and I want to use text from this array to create dynamic component. Idea is: I can see text[0], I click some button then I can see text[1], something like loop. How can to do it?
You can do this way maybe.
const DATA_Section = [
{
id: 0,
text: "Some text1",
},
{
id: 1,
text: "Some text2",
},
{
id: 2,
text: "Some text 3",
},
];
const Component: FC = () => {
const [initialRenderState, setInitialRenderState] =
useState(DATA_Section[0])
return (
<View>
{intialRenderState.map(item => <Text key={item.id}>{item.text}</Text>)}
<Pressable onPress={()=> setInitialRenderState(prevState => [...prevState, Data_Section[prevState.length]])}>
<Text>Add item</Text>
<Pressable>
</View>
)
}
const DATA_Section = [
{
id: 0,
text: "Some text1",
},
{
id: 1,
text: "Some text2",
},
{
id: 2,
text: "Some text 3",
},
];
const sample = () => {
const [ index, setIndex ] = useState(0);
const handleChangeIndex = () => {
if (index < DATA_Section.length) {
setIndex(index + 1);
}else{
setIndex(0);
}
}
return(
<View style={{flex:1}}>
<TouchableWithoutFeedback onPress={()=>handleChangeIndex()}>
<View style={{width:100, height:50, backgroundColor:'red'}}>
<Text>
{DATA_Section[index].text}
</Text>
</View>
</TouchableWithoutFeedback>
</View>
);
}

Why does my FlatList not scroll in React Native?

I have seen a lot of answers for FlatList not scrolling for React Native, but none of the answers seem to work.
The answer usually says to add flex: 1 to the parent, but here is an example that has that and it still doesn't scroll.
You can test here, but make sure you select iOS or Android. It does scroll for web.
import React from 'react';
import { SafeAreaView, View, FlatList, StyleSheet, Text, StatusBar } from 'react-native';
const DATA = [
{
id: "bd7acbea-c1b1-46c2-aed5-3ad53abb28ba",
title: "First Item",
},
{
id: "3ac68afc-c605-48d3-a4f8-fbd91aa97f63",
title: "Second Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d72",
title: "Third Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d73",
title: "Fourth Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d74",
title: "Fifth Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d75",
title: "Sixth Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d76",
title: "Seventh Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d77",
title: "Eighth Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d77",
title: "Ninth Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d77",
title: "Tenth Item",
},
];
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const App = () => {
const renderItem = ({ item }) => (
<Item title={item.title} />
);
return (
<SafeAreaView style={styles.container}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: StatusBar.currentHeight || 0,
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 32,
},
});
export default App;
Modify the SafeAreaView to be within it's own container and added a View container with flexGrow: 1 instead of flex: 1:
const App = () => {
return (
<SafeAreaView style={styles.safe}>
<View style={styles.container}>
<FlatList
data={data}
renderItem={({ item }) => (<Item title={item.title} />) }
keyExtractor={item => item.id}
/>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
safe: {
marginTop: StatusBar.currentHeight || 0,
},
container: {
flexGrow: 1
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 32,
},
});
Above worked in a snack Expo testing for iOS. Entire sample code with a little re-write:
import React from 'react';
import { SafeAreaView, View, FlatList, StyleSheet, Text, StatusBar } from 'react-native';
const data = [
{
id: "bd7acbea-c1b1-46c2-aed5-3ad53abb28ba",
title: "First Item",
},
{
id: "3ac68afc-c605-48d3-a4f8-fbd91aa97f63",
title: "Second Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d72",
title: "Third Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d73",
title: "Fourth Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d74",
title: "Fifth Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d75",
title: "Sixth Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d76",
title: "Seventh Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d77",
title: "Eighth Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d77",
title: "Ninth Item",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d77",
title: "Tenth Item",
},
];
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const App = () => {
return (
<SafeAreaView style={styles.safe}>
<View style={styles.container}>
<FlatList
data={data}
renderItem={({ item }) => (<Item title={item.title} />) }
keyExtractor={item => item.id}
/>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
safe: {
marginTop: StatusBar.currentHeight || 0,
},
container: {
flexGrow: 1
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 32,
},
});
export default App

How can I display the image stored in a array

I need to show a flag icon along with the text. I have all the image stored in assets folder. These are the codes that I have tried so far. I have stored the icon in a const and which I later think of calling. I want to show icon and lang horizontally with icon at left side.
const language = [
{ lang: "English", code: "en", icon: require(`../assets/us.png`) },
{ lang: "French", code: "fr", icon: require(`../assets/th.png`) },
{ lang: "Japanese", code: "jp", icon: require(`../assets/jp.png`) },
]
class App extends Component {
onSelectLanguage = () => {
return (
language.map((data, i) => {
return (
<View key={i} style={styles.dropDownView}>
<TouchableOpacity onPress={() => this.onSelectedLang(data)}>
<Text style={styles.dropDownText}><Image source="{data.icon}" />{data.lang}</Text>
</TouchableOpacity>
</View>
)
})
)
}
}
Try this code:
const language = [
{ lang: "English", code: "en", icon: require(`../assets/us.png`) },
{ lang: "French", code: "fr", icon: require(`../assets/th.png`) },
{ lang: "Japanese", code: "jp", icon: require(`../assets/jp.png`) },
]
class App extends Component {
onSelectLanguage = () => {
return (
language.map((data, i) => {
return (
<View key={i} style={styles.dropDownView}>
<TouchableOpacity onPress={() => this.onSelectedLang(data)}>
<Text style={styles.dropDownText}><Image source={data.icon} />{data.lang}</Text>
</TouchableOpacity>
</View>
)
})
)
}}

React Native Mapping through Array of Objects gives blank values

I'm trying to loop over a simple array of objects and display the content inside Text tag. The looping is being done but the actual content is not displayed on the screen.
import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
export default class App extends React.Component {
render() {
const initialArr = [
{
id: 1,
color: "blue",
text: "text1"
},
{
id: 2,
color: "red",
text: "text2"
},
{
id: 3,
color: "green",
text: "text3"
}
];
return (
<View style={styles.container}>
{initialArr.map((prop, key) => {
return (
<Text key={key}>{prop[1]}</Text>
);
})}
</View>
);
}
}
render() {
const initialArr = [
{ id: 1, color: "blue", text: "text1" },
{ id: 2, color: "red", text: "text2" },
{ id: 3, color: "green", text: "text3"}
];
var textInputComponents = initialArr.map((prop, key) => {
return (
<Text key={key}>{prop.text}</Text>
);
})
return (
<View style={styles.container}>
{textInputComponents}
</View>
);
}
Apart from John_mc's answer, instead of using inline mapping, you can also split the mapping operation, to improve readability and maintainability

Resources