OnPress change the style of component from loop- React Native with hooks - react-native

So I am pretty new in react native, I am trying to develop a quiz game, where users will be given Set of answers. I want to select change the color of the component when it is pressed by the user, kind of toggle it. So far I came up with useState solution, but unfortunately cannot figure out how to exclude the change of color, I guess I need to follow indexing or something, can anyone please make me understand the process with the solution.
export const QuizScreen = ({ navigation,route }) => {
const [quizArray, setQuizArray] = React.useState([])
const [rightAnswer, setRightAnswer]= React.useState(false)
const [selectBtn, setSelectBtn] = React.useState("#fff")
return(
<View>
{quizArray[qno].answer.map(r=>
<TouchableHighlight style={[styles.listItem, {backgroundColor:selectBtn}]}
onPress={()=>{
setRightAnswer(r.rightAnswer)
setSelectBtn("#DDDDDD") //so this changes logically all the component from the list
}}
activeOpacity={0.6} underlayColor="#DDDDDD"
>
<Text>{r.option}</Text>
</TouchableHighlight>
)}
</View>
I need to know how do i implement the background change for only one and kinda make it toggle everytime user select or deselect. Thank you

You were right about using an index for determining the clicked list item.
You can change the color by storing the index of the selected item using selectBtn state and then using that state set the backgroundColor accordingly.
Here is how you can do it:
export const QuizScreen = ({ navigation, route }) => {
const [quizArray, setQuizArray] = React.useState([]);
const [rightAnswer, setRightAnswer] = React.useState(false);
const [selectBtn, setSelectBtn] = React.useState(null);
return (
<View>
{quizArray[qno].answer.map((r, index) => (
<TouchableHighlight
style={[
styles.listItem,
{ backgroundColor: selectBtn === index ? '#dddddd' : '#fff' },
]}
onPress={() => {
setRightAnswer(r.rightAnswer);
setSelectBtn(index);
}}
activeOpacity={0.6}
underlayColor="#DDDDDD">
<Text>{r.option}</Text>
</TouchableHighlight>
))}
</View>
);
};
Here is the working example: Expo Snack
2

Related

Dimesnion from React/native not working properly on Android device

I am trying to create an Instagram like reel functionality in react-native app.
I want to display a video element on entire screen available space.
For the purpose of it I am using a FlatList.
This code doesn't work on every device.
'const HomeScreen = ({navigation}) => {
const dataArray=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19];
const renderItem=({item,index})=>{
return(
<View style={[{height:Dimesnions.get('window').height-bottomtabBarHeight,borderBottomColor:'black'},]}>
<Text>{item}</Text>
</View>
)
}
return (
<SafeAreaView style={styles.container}>
<StatusBar />
<FlatList
data={dataArray}
renderItem={renderItem}
pagingEnabled
decelerationRate={'fast'}
/>
</SafeAreaView>
)
}
export default HomeScreen
const styles = StyleSheet.create({
container:{
flex:1,
}
})`
work for me
import dimansion from react native
const {height,width} = dimansion.get('window')
const Myfun=()=>{
return(
<View style={{flex:1}}>
<View style={{height:height}}></View>
</View>
)
}
actually what you need to do is make a state variable and assign dimension's value in it and then in useEffect make a listener which would be triggered every time your device rotates or dimensions of device gets changed
example below:
const [dim, setDim] = useState(Dimensions.get('screen');
useEffect(() => {
const subscription = Dimensions.addEventListener(
'change',
({ screen}) => {
setDim({ screen});
},
);
return () => subscription?.remove(); });
you can use dim.height for height and dim.width for width in your desired component
PS. I have made a complete and comprehensive tutorials regarding Dimensions API if you want to check it out then link is below(Tutorial is in Urdu/Hindi)
https://www.youtube.com/watch?v=mISuyh_8-Cs&t=605s&ab_channel=LearnByDill
I think it is because of typo which you used Dimesnions instead of Dimensions. You can get height or width of device as below:
const ScreenWidth = Dimensions.get('window').width;
const ScreenHeight = Dimensions.get('window').height;

react native textinput lost focus after 1 char type

I have this problem with ios but not with android. It only disturb the add task input the task edit and the list name edit. The input addList(It's the one with "What to do?" on the draw) in the header works fine.
UI drawing
Achitecture of components
I console log my component and I can see it rerender everytime I add a letter in the input field.
I checked on google and follow this:(can we link other website here?) https://www.codegrepper.com/code-examples/javascript/react+native+textinput+lost+focus+after+charter+type
Tried the the first solution with onBlurr and onFocus.
I tried to make a TextInput component for add task.
I even try with my component addList but it didn't solve the problem.
Anyone have faced this problem before? Is there anyway to by pass this?
My code without the import/style look like this:
const TaskList: FunctionComponent<TasksListProps> = ({
addTask,
deleteTask,
toggleTask,
editTaskName,
...props
}) => {
console.log('props', props);
const [nameOfTask, setNameOfTask] = useState('');
console.log('name', nameOfTask);
const textHandler = (enteredName: string) => {
setNameOfTask(enteredName);
};
const handleSubmitTask = () => {
if (nameOfTask === '') {
return;
}
addTask(props.listId, nameOfTask);
setNameOfTask('');
};
return (
<View style={styles.tasksListContainer}>
{props.tasks.map(task => (
<SingleTask
key={task.id}
task={task}
listId={props.listId}
deleteTask={deleteTask}
toggleTask={toggleTask}
editTaskName={editTaskName}
/>
))}
<View style={styles.taskInputContainer}>
<TextInput
style={styles.tasksTextInput}
value={nameOfTask}
onChangeText={textHandler}
placeholder="Write a task to do"
/>
<TouchableOpacity onPress={handleSubmitTask}>
<Image source={require('./Img/add-button.png')} />
</TouchableOpacity>
</View>
</View>
);
};
You can create a HOC and wrap your screen width DismissKeyboard
import { Keyboard } from 'react-native';
const DismissKeyboard = ({ children }) => (
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
{children}
</TouchableWithoutFeedback>
);
That because Re render.
Try to make the input with the main component of the page to test it.
Then check where the error with re-render

How to animate multiple entries using react native reanimated v2?

I am new to reanimated. I am now trying to reanimating multiple items.
These items will not show up at the start of the component on load.
But they items will show up when the corresponding item is pressed that my intentions.
eg when button 1 is pressed item 1 will pop up slowly, button 2 is pressed and item 2
like that.
I got 2 component in my app
ItemsList screen and item component
I don't have any animation code in itemList screen. I am just retuning the item component.
{items.map(item => (
<OtherItem
key={item._id}
item={item}
selectedItem={selectedItem}
setSelectedItem={setSelectedItem}
/>
))}
Inside the item component.I got a sharedValue, useEffecthooks, and useState which I use to animate according to user interaction.
ITEM COMPONENT
const [selected, setSelected] = useState(false);
const [count, setCount] = useState(1);
// Animation
const progress = useSharedValue(0);
const reAnimatedStyle = useAnimatedStyle(() => {
return {
opacity: progress.value,
transform: [{scale: progress.value}],
};
});
useEffect(() => {
progress.value = withTiming(1, {duration: 2000});
}, [selected]);
return (
<TouchableOpacity
onPress={() => selectItem(item)}
style={[
globalStyle.pageContainer,
]}>
{selected && (
<Animated.View
style={[
{flexDirection: 'row', alignItems: 'center'},
reAnimatedStyle,
]}>
...
</Animated.View>
)}
</TouchableOpacity>)
As you can see in the code, My intention is that when user press button 1 hidden details inside button 1 will show up.
But the thing Is only the first time works. I think its because of the shared value. What I want is I want every item to work. So can any one suggest the solution
Here is one of many solutions.
Track the selected item on "ItemsList screen" using the following snippet
const [selectedId, setSelectedId] = useState(null);
const handleSelection = (id) => setSelectedId(id);
return (
<SafeAreaView style={styles.container}>
{ITEMS.map((item) => (
<OtherItem
key={item._id}
item={item}
handleSelection={handleSelection}
selectedId={selectedId}
/>
))}
<StatusBar style="auto" />
</SafeAreaView>
);
}
on "ITEM COMPONENT" screen, use useEffect to change progress.value. If the "ITEM COMPONENT" sees that the current rendered item is selected then it will increase the progreass.value 1 other wise it will decrease it to 0.
use the following snippet
useEffect(() => {
if (selectedId === item._id)
progress.value = withTiming(1, { duration: 2000 });
else progress.value = withTiming(0, { duration: 2000 }); // un comment this line if you want to see hidden element of just one item and hide the other item
}, [selectedId]);
also send a function ( handleSelection in this example ) from "ITEM LIST screen" to "ITEM COMPONENT" screen to track which item is selected.
Here is a expo snack with full source code.

React native Flatlist not re-rendering on state change

I realize there are a lot of questions and answers about this out there but I am fairly new to react native and most of the answers are dealing with React Components and not hooks. In the following example availableInterests is pulled from a firestore database call. Then we loop through the availableInterests so the user can select the their interests from the Flatlist of interests. Everything works great except the FLatlist does not re-render so the button that is used to select currentInterests never shows the change that an interest has been selected. Does anyone see what I am missing here?
const [availableInterests, setAvailableInterests] = useState([]);
const [currentInterests, setCurrentInterests] = useState([]);
const selectThisInterest = (item) => {
let myInterests = currentInterests;
if(myInterests.includes(item.id)) {
myInterests.pop(item.id);
} else {
myInterests.push(item.id);
}
setCurrentInterests(myInterests);
}
return <View>
<Text style={styles.text}>Select Your Interests:</Text>
<FlatList
data={availableInterests}
keyExtractor={(item, index) => index.toString()}
extraData={currentInterests}
renderItem={({ item, index }) =>
<View key={item.id}>
<Text>{item.title}</Text>
<Text>{item.description}</Text>
<Image
source={{ uri: item.icon }}
style={{ width: 100, height: 100}}
/>
<TouchableOpacity onPress={() => selectThisInterest(item)}>
<Text style={styles.buttonText}>{`${currentInterests.includes(item.id) ? 'UnSelect' : 'Select'}`}</Text>
<Text>{item.id}</Text>
</TouchableOpacity>
</View>
}>
</FlatList>
</View>
put this state below
const [currentInterests, setCurrentInterests] = useState([]);
const [extra, setExtra] = useState(0);
at the end of your function just put this
const selectThisInterest = (item) => {
....
setExtra(extra + 1)
}
I think the mistake is in your selectThisInterest function. When you are updating the currentInterests based on previous value, React doesn't recognises such a change because you are simply assigning myInterests with your currentInterests.
What you want to do is to copy that array and assign it to myInteresets and then update your values to the new copied array. Once the calculation are completed on the new myInteresets array, the setCurrentInterests() will re-render the app because now React recognises there is a change in the state.
To copy the array, you can use,
let myInterests = [...currentInterests];
change your selectThisInterest function to reflect this change,
const selectThisInterest = (item) => {
let myInterests = [...currentInterests];
if(myInterests.includes(item.id)) {
myInterests.pop(item.id);
} else {
myInterests.push(item.id);
}
setCurrentInterests(myInterests);
}

Change only one icon in a list of objects - React Native

I want to be able to change the icon in a list of todos (see picture) from an exclamation mark, to a checkmark. That should happen if the user puts the finger on the icon, or the developer clicks with the mouse in the emulator.
Through the code below, I manage to change it, but the new icon only appears if I close the modal containing the list, and reopen it. So the modal does not re-render, neither partly nor in whole.
How can I make the changes appear live, immediately after I click the exclamation icon? I suspect it has to do with state, but it doesn't seem possible to create a React hook inside the map function. If I let onPress call a function, then the state is only known within that external function, and I don't know how to export it.
export const TeacherMessages = (props) => {
return (
<View
style={[
styles.borderBox,
props.todos.length > 0 || props.notes.length > 0
? styles.whiteBox
: null,
]}
>
{
props.todos.map((todo) => (
<View key={todo.id} style={styles.listNotes}>
<AntDesign
style={styles.listIcon}
onPress={() => todo.isChecked = true}
name={todo.isChecked ? "checksquare" : "exclamationcircle"}
color={todo.isChecked ? "green" : "red"}
size={18}
/>
<Text style={styles.listText}> {todo.description}</Text>
</View>
))
}
);
I think you need to store the todos array in a react hook so that way the changes you do to it becomes live instantly, You can have this changeTodo function in the parent component and pass it as props to call it from the child component with the index needed. I think this might help:
export const TeacherMessages = (props) => {
const [todosArr, setTodosArr] = React.useState(props.todos)
const checkTodo = (todoIndex) =>{
let arr = [...todosArr]
arr[todoIndex].isChecked= true
setTodosArr(arr)
}
return (
<View
style={[
styles.borderBox,
todosArr.length > 0 || props.notes.length > 0
? styles.whiteBox
: null,
]}
>
{
todosArr.map((todo, index) => (
<View key={todo.id} style={styles.listNotes}>
<AntDesign
style={styles.listIcon}
onPress={() => checkTodo(index)}
name={todo.isChecked ? "checksquare" : "exclamationcircle"}
color={todo.isChecked ? "green" : "red"}
size={18}
/>
<Text style={styles.listText}> {todo.description}</Text>
</View>
))
}
);