useState getting confused when using multiple components - react-native

The action taken to update the state in a functional component is incorrectly updating the state of the final component loaded.
I have simplified what I'm doing to very simple code. I'm wondering if I'm missing something in why this does not work
This is parent component that loops to create subcomponents.
{scoreDays.length >0 ? scoreDays.map((el,idx) =>(
<ScoringDay key={idx} date ={el.day} score={el.score}
channels={el.channels} />
)) : null}
This is the ScoringDay component. I'm simply using a button press to update the text in state and have it displayed.
const ScoringDay = props => {
[expanded, setExpanded] = useState(false);
[test, setTest] = useState('starting text');
return(
<View>
<Text>
{test}
</Text>
<Text onPress={() =>setTest('Clicked here')}>Update value</Text>
</View>
)
In my example, 3 "ScoringDay" components are shown on the screen. However, no matter which text "Update value" I click, it always updates the text on the last component.
Why isn't the action being applied to the correct component?? I am using an index on the key...but not sure what else needs to be changed here?

Run the status change from parent to child.
[testtext, setTestText] = useState('starting text');
...
{scoreDays.length >0 ? scoreDays.map((el,idx) =>(
<ScoringDay key={idx} date ={el.day} score={el.score}
channels={el.channels} testtext={testtext} setTestText={setTestText} />
)) : null}
Used
const ScoringDay = props => {
[expanded, setExpanded] = useState(false);
const { testtext, setTestText} = props
return(
<View>
<Text>
{testtext}
</Text>
<Text onPress={() =>setTestText('Clicked here')}>Update value</Text>
</View>
)

Related

Pushing a flatlist item up or down

I have a flatlist. The flatlist contains todos that I can add, edit and delete.
I have two buttons on each todo-item, one for up and one for down.
If I want to push them up 1 step (until it reaches 0) or down (until it reaches the last item) how would I do this? Help is much appreciated, this feels basic but I can't seem to find an answer that works.
Here is my code:
<FlatList
removeClippedSubviews={false}
data = {todos.sort(function(a,b){return b.date - a.date})}
keyExtractor={(item)=>item.key}
renderItem={({item})=>{
return(
<TouchableOpacity>
<View>
<TextInput
style={styles.toDoItem}
value={item.title}
onChangeText={(text)=>handleUpdateText(text, item)}
multiline={true}
maxLength={55}
blurOnSubmit={true}
/>
<TouchableOpacity onPress={()=>handleMoveUp(item.key, item)}>
<FontAwesome name="plus-circle" size={30} color={black}/>
</TouchableOpacity>
<TouchableOpacity onPress={()=>handleMoveDown(item.key, item)}>
<FontAwesome name="minus-circle" size={50} color={black}/>
</TouchableOpacity>
The FlatList's data must first be stored into a state.
const [todos, setTodos] = useState([])
Since you are sorting the initial states in your FlatList according to their dates, it might be useful to do this exactly once in the following useEffect.
React.useEffect(() => {
let temp = [...todos]
temp.sort(function(a,b){return b.date - a.date})
setTodos(temp)
}, [setTodos])
We could also do this directly in the initial state.
Use todos of your state in your FlatList.
<FlatList
data = {todos}
...
</FlatList
...
Then, implement a function that alters the state of your items as follows.
const handleMoveUp = React.useCallback(
(index) => {
if (index !== 0) {
let prevState = [...todos]
let temp = prevState[index - 1]
prevState[index - 1] = prevState[index]
prevState[index] = temp
setItems(prevState)
}
},
[setTodos, todos]
)
add this function as the onPress function of your TouchableOpacity that moves items up, that is
<TouchableOpacity onPress={()=>handleMoveUp(item.key)}>
<FontAwesome name="plus-circle" size={30} color={black}/>
</TouchableOpacity>
The process for moving down is implemented in a very similar fashion. Just use the same code as in handleMoveUp but increase the index instead of decreasing it.

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

creating component in onPress event

I'm trying to build an android app using functional react-native and expo.
I want to know to create new components inside other components when events are triggered.
What I'm trying to do is the following
The code I wrote is below:
<View>
<TouchableOpacity style = {styles.container} onPress = {() => {return (
<TouchabeOpacity>
<Text> Hello World </Text>
</TouchabeOpacity> ) }}
</ToucchabeOpacity>
</View>
I want when pressing the first touchableopacity another one would be created and displayed.
Any solutions ?
You need to do something like:
import {NewComponent} from './components/NewComponent';
....
const[displayNewComponent, setDisplayNewComponent] = useState(false)
...
<View>
<TouchableOpacity style = {styles.container} onPress={() => setDisplayNewComponent(true)}
{ displayNewComponent && <NewComponent /> }
</ToucchabeOpacity>
</View>
export const NewComponent = () => {
return (
<TouchabeOpacity>
<Text> Hello World </Text>
</TouchabeOpacity>
)

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

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

Updating button state which is clicked, inside RenderItem of Flat-list, in React Native

I am new to React Native and trying to accomplish something like below, where simple list from server is rendering in a list with a button. Button, upon click, will be disabled and opacity will be changed.
I am able to create the UI but when I click any button that says Join, previous clicked button resets it state to original. In other words, only one button displays clicked state all the time.
so my code is something like
constructor(props){
super(props);
this.state = {selectedIndices: false, groupsData: ''};
}
Flatlist inside render method looks like
<FlatList style={styles.list}
data={this.state.groupsData}
keyExtractor={(groups) => {
return groups.groupId.toString();
}}
renderItem={(item, index) => {
return this.renderGroupItem(item);
}}/>
RenderGroupItem
renderGroupItem = ({item} )=>(
<GroupItem group = {item} style={{height: '10%'}} onPress = {() => this.onJoinButtonPress(item)}
index = {this.state.selectedIndices}/>
)
onJoinButtonPress
onJoinButtonPress = (item) =>{
this.setState({selectedIndices: true});
}
GroupItem
render(){
if(this.props.group.groupId === this.props.index){
return(
<View style = {[styles.container]}>
<Image source={{uri: 'some Image Url'}} style={styles.roundImage}/>
<Text style={styles.groupText}>{this.props.group.name}</Text>
<View >
<TouchableOpacity style = {[styles.button, {opacity: 0.4}]} activeOpacity = { .5 } onPress = {this.props.onPress}
disabled = {true}>
<Text style = {{color: 'white', fontSize: 12}}>Joined</Text>
</TouchableOpacity>
</View>
</View>
);
}else{
return(
<View style = {styles.container}>
<Image source={{uri: 'Some Image Url'}} style={styles.roundImage}/>
<Text style={styles.groupText}>{this.props.group.name}</Text>
<View >
<TouchableOpacity style = {styles.button} activeOpacity = { .5 } onPress = {this.props.onPress}>
<Text style = {{color: 'white', fontSize: 12}}>Join</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
Now I know that I need to pass an array or hasmap which contains mapping of items that have been clicked but I dont know how to do that. Need desperate help here.
I was able to overcome above problem after maintaining a boolean in the groupsData. Upon selection, I update a boolean "groupsJoined" in groupsData and update the state of groupsData which will invoke render. Inside GroupsItem class, I added a check that if data from props has joinedGroups as true then render selected state else non selected state.
Courtesy https://hackernoon.com/how-to-highlight-and-multi-select-items-in-a-flatlist-component-react-native-1ca416dec4bc