React native: Update value of object in array in state - javascript

I have a component which changes the state when checkbox is checked and the data needs to be updated of the object in the array.
The component state looks something like this
{
key:1,
todo:"Something",
isChecked:false
}
i have 3 files:
AddTodo.js Which passes state & setState to an component TodoList which passes it the subcomponent TodoItem.
I am unable to update the state from TodoItem , I need to implement a function that finds the object from array and updates its isChecked state.
AddTodo.js
function AddTodo() {
const [state, setState] = useState(false);
const [todos, addTodos] = useState([]);
var keys = (todos || []).length;
return (
<View style={styles.container}>
<Modal
animationType="slide"
transparent={true}
visible={state}
statusBarTranslucent={true}
>
<View style={styles.itemsContainer}>
<GetInfoDialog
state={state}
stateChange={setState}
addItem={addTodos}
numKeys={keys}
/>
</View>
</Modal>
{(todos || []).length > 0 ? (
<TodoList data={todos} updateState={addTodos} />
) : null}
<TouchableOpacity
style={styles.btn}
onPress={() => {
setState(true);
}}
>
<Text style={styles.text}>Add New</Text>
</TouchableOpacity>
</View>
);
}
TodoList.js
function TodoList(props) {
return (
<View style={styles.todoList}>
<FlatList
data={props.data}
renderItem={({ item }) => {
console.log(item);
return (
<TodoItem
list={props.data}
itemKey={item.key}
todo={item.todo}
isChecked={item.isChecked}
updateState={props.updateState}
/>
);
}}
backgroundColor={"#000000"}
alignItems={"center"}
justifyContent={"space-between"}
/>
</View>
);
}
TodoItem.js
function TodoItem(props) {
const [checked, setCheck] = useState(props.isChecked);
return (
<View style={styles.todoItem}>
<Checkbox
value={checked}
onValueChange={() => {
setCheck(!checked);
}}
style={styles.checkbox}
/>
<Text style={styles.text}>{props.todo}</Text>
</View>
);
}

renderItem={({ item, index }) => {
console.log(item);
return (
<TodoItem
list={props.data}
itemKey={item.key}
todo={item.todo}
isChecked={item.isChecked}
updateState={props.updateState}
setChecked={(value)=>{
let updatedList = [...yourTodosList]
updatedlist[index].isChecked=value
setTodos(updatedList)
}}
/>
);
}}
and in your todo item
onValueChange={(value) => {
props.setChecked(value);
}}
i also don't think that you need an is checked state in your todo component since you are passing that through props (so delete const [checked, setCheck] = useState(props.isChecked) line and just use the value you are getting from props.isChecked)
didn't pay much attention to your variable names but this should put you on the right track

as per React Native Hooks you have to call
useEffect(() => {
setCheck(checked);
}, [checked]) // now this listens to changes in contact
in TodoItem.tsx

Related

Flatlist Item in search results needs to be tapped twice for onPress to register

I have a react native modal with a search bar and a flatlist that shows results. The search result in the flatlist has to be tapped twice for the click to register. I need to figure out how to make it work on first click. Here is the code
const Item = ({ item, onPress, value }) => (
<TouchableOpacity style={styles.modalItemStyle} onPress={onPress}>
<View style={styles.modalIconStyle}>
{item.id === value && <Icon name="sheep" size={20} color="#68c25a" />}
</View>
<Text style={styles.modalItemTextStyle}>{item.title}</Text>
</TouchableOpacity>
);
const MyDropdown = ({
data,
label,
field,
onSelect,
value,
searchable = true,
}) => {
const [modalOpen, setModalOpen] = useState(false);
const [selectedValue, setSelectedValue] = useState(value);
const [query, setQuery] = useState("");
const [modalData, setModalData] = useState(data);
useEffect(() => {
if (query.length > 0) {
const filteredData = data.filter((item) =>
item.title.toLowerCase().includes(query.toLowerCase())
);
setModalData(filteredData);
} else {
setModalData(data);
}
}, [query]);
const inputRef = useRef(null);
const searchRef = useRef(null);
const renderItem = ({ item }) => {
return (
<Item
item={item}
value={selectedValue.id}
onPress={() => {
inputRef.current.blur();
Keyboard.dismiss();
setQuery("");
setSelectedValue(item);
setModalOpen(false);
}}
/>
);
};
return (
<View style={styles.selectContainer}>
<TextInput
ref={inputRef}
//react native paper text input with value and label
label={label}
value={selectedValue.title}
style={styles.sheepTextInput}
mode="outlined"
onChangeText={(text) => onSelect(text)}
showSoftInputOnFocus={false}
onFocus={() => {
setModalOpen(true);
inputRef.current.blur();
}}
></TextInput>
<Modal height="auto" isVisible={modalOpen}>
<View style={styles.modal}>
{searchable && (
<View>
<TextInput
ref={searchRef}
mode="outlined"
outlineColor="#68c25a"
activeOutlineColor="#68c25a"
style={styles.modalSearch}
value={query}
onChangeText={(q) => setQuery(q)}
placeholder="Search"
//add clear button
right={
<TextInput.Icon
name="close"
color="#68c25a"
onPress={() => {
setQuery("");
}}
/>
}
></TextInput>
</View>
)}
<FlatList
keyboardShouldPersistTaps="always"
data={modalData}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
</View>
</Modal>
</View>
);
};
I tried adding keyboardShouldPersistTaps with different options to flatlist, I also tried to blur through refs (searchref), but none of those approaches worked. What am I doing wrong?
keyboardShouldPersistTaps should be "handled" if you want it to work on first tap.
<FlatList
keyboardShouldPersistTaps="handled"
data={modalData}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
I managed to fix this by setting the keyboardshouldpersisttaps property to handled on every scrollview, including the flatlist in question and the component that renders all of those inputs in my form. I also had to wrap the flatlist with the search input in a scrollview. Of course I now have a virtualized lists should never be nested warning, but at least the taps now work.

Changing style of specific component returned from map function onClick

I am trying to change the style of individual TouchableOpacity components that have been returned from a map function.
Here is the component:
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
return (
<React.Fragment key={id}>
<TouchableOpacity
style={styles.button}
onPress={() => console.log(id)}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
</React.Fragment>
);
})}
</View>
);
};
Let TouchableOpacity = TO.
The map function returns about 30 TOs with unique IDs. When I click the TOs, I can see their unique ID in the console log. I want to know how I can modify the style of an individual TO.
Here is my render function which uses the functional component Example.
render() {
return (
<View style={styles.body}>
<ScrollView>
<View style={styles.column}>
<this.Example props={{ listExample: this.getList() }} />
</View>
</ScrollView>
</View>
);
}
What I have tried:
referencing this stackoverflow post, I tried to create a function which changed the style of the TO when it is clicked. But the result of this changed all the TOs in the UI since of the way it is mapped.
I tried something like the following.
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
let buttonStyle = this.state.pressed ? styles.button : styles.buttonClicked
return (
<React.Fragment key={id}>
<TouchableOpacity
style={buttonStyle}
onPress={() => console.log(id)}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
</React.Fragment>
);
})}
</View>
);
};
But as previously stated, this changed all of the Touchable Opacitys. Is there a way to only change one?
Thanks
Edit - to show entire class
class Page extends Component {
constructor(props) {
super(props)
}
MyButton = ({ onButtonPressed = () => {} }) => {
const [isPressed, setIsPressed] = useState(false);
const onPressed = () => {
setIsPressed(!isPressed);
onButtonPressed();
}
return (<TouchableOpacity style={isPressed ? styles.pressedButton: styles.button}
onPress={onPressed}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
);
}
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
return (
<MyButton key={id}/>
);
})}
</View>
);
};
render() {
return (
<View style={styles.body}>
<ScrollView>
<View style={styles.column}>
<this.Example props={{ listExample: this.getList()}} />
</View>
</ScrollView>
</View>
);
}
}
It is easier to separate the component inside map to a separate component and then handle style changes on press there
const MyButton = ({ onButtonPressed = () => {} }) => {
const [isPressed, setIsPressed] = useState(false);
const onPressed = () => {
setIsPressed(!isPressed);
onButtonPressed();
}
return (<TouchableOpacity style={isPressed ? styles.pressedButton: styles.button}
onPress={onPressed}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
)
}
so you can use in the map like this
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
return (
<MyButton key={id} />
);
})}
</View>
);
};

change Icon after clicking on it

I have a component that looks like this:
const criteriaList = [
'Nur Frauen',
'Freunde Zweiten Grades',
];
export const FilterCriteriaList: React.FunctionComponent = () => {
const [state, setState] = useState(false);
useEffect(() => {
console.log('state is,', state);
});
const myFunction = () => {
console.log('checking state', state);
if (state == false) {
setState(true);
} else {
setState(false);
}
};
return (
<View style={styles.container}>
<View style={styles.horizontalLine} />
{criteriaList.map((item: string, index: number) => (
<View key={index}>
<View style={styles.criteriaRow}>
<TouchableOpacity
onPress={() => {
myFunction();
}}>
<Icon
name="circle-thin"
color="#31C283"
size={moderateScale(20)}
/>
</TouchableOpacity>
<Text style={styles.text}>{item}</Text>
</View>
<View style={styles.horizontalLine} />
</View>
))}
</View>
);
};
Currently, I am using the circle-thin icon. I want to change it such that everytime I click on an icon, it changes to the dot-circle-o icon. Like radio buttons. However, I am not quite sure how to do so.
I thought of using ternary operators but since I am mapping my fields Idk how to setStates collectively. Maybe using the index? I don't want to make a separate state for each field. Here's a similar snack demo:
https://snack.expo.io/toTSYc2fD
I want to be able to select multiple/unselect options. I don't want to apply the same rule on all fields together.
Note: the onPress function can also be used on the Icon directly instead of the TouchableOpacity (though it is not preferred)
Using a ternary sounds like the right approach to me. Can you not do something like:
name={state ? 'dot-circle-o' : 'circle-thin'}
You could also refactor your function:
const myFunction = () => {
console.log('checking state', state);
setState(!state)
};
If you have multiple fields then there are many ways to handle it. You could call useState multiple times, eg:
const [field1, setField1] = useState(false);
const [field2, setField2] = useState(false);
You could also store all fields in the same state:
const [state, setState] = useState({field1: false, field2: false});
...
const myFunction = (fieldName) => {
console.log('checking state', state);
setState({...state, [fieldName]: !state[fieldName]})
};
I guess you'd then use the item as the "fieldName? In which case:
return (
<View style={styles.container}>
<View style={styles.horizontalLine} />
{criteriaList.map((item: string, index: number) => (
<View key={index}>
<View style={styles.criteriaRow}>
<TouchableOpacity
onPress={() => {
myFunction(item);
}}>
<Icon
name={state[item] ? 'dot-circle-o' : 'circle-thin'}
color="#31C283"
size={moderateScale(20)}
/>
</TouchableOpacity>
<Text style={styles.text}>{item}</Text>
</View>
<View style={styles.horizontalLine} />
</View>
))}
</View>
);
And to create the initial state:
const initialState = {}
criteriaList.forEach(item => initialState[item] = false)
const [state, setState] = useState(initialState);
The code would be something like below.
You have to set the index of the selected item as the state and use it to chose the icon.
const criteriaList = [
{title:'My List',checked:false},
{title:'Friends listt',checked:false},
{title:'Not Good',checked:false},
{title:'Sweet and sour',checked:false},
{title:'Automatic',checked:false},
];
export const FilterCriteriaList: React.FunctionComponent = () => {
const [state, setState] = useState(criteriaList);
useEffect(() => {
console.log('state is,', state);
});
const myFunction = (index) => {
console.log('checking state', state);
const arr=[...state];
arr[index].checked=arr[index].checked?false:true;
setState(arr);
};
return (
<View style={styles.container}>
<View style={styles.horizontalLine} />
{criteriaList.map((item: Any,index:number) => (
<View key={item}>
<View key={item} style={styles.criteriaRow}>
<Icon
style={styles.icon}
name={item.checked?"circle":"circle-thin"}
color="#31C283"
size={moderateScale(20)}
onPress= {()=>myFunction(index)}
/>
<Text style={styles.text}>{item.title}</Text>
</View>
<View style={styles.horizontalLine} />
</View>
))}
</View>
);
};

How to make the react native switch to true when list of toggle button is rendered through a map?

Problem:
I am rendering a set of toggle buttons through a map. Now I want to make it true or false each when the user is changing the value of each toggle. This is how I have created the toggle component.
const AnswerToggle = (props) => {
const {styles, name} = props;
return (
<View style={styles.answerContentContainer}>
<View style={styles.answerTextContainer}>
<AppText styles={styles.answerText}>{name}</AppText>
</View>
<View style={styles.container}>
<Switch
trackColor={{false: '#dddddd', true: '#c1d6ee'}}
thumbColor={{false: '#ffffff', true: '#007aff'}}
ios_backgroundColor="#dddddd"
// ref={name}
onValueChange={
(value) => {
// ref[name].value = true;
}
// console.log(
// '>>>>>> value',
// this[`${name}`].value,
// )
}
style={styles.toggle}
/>
</View>
</View>
);
};
And I am loading it through map like this.
return answers.map((answer, i) => {
return (
<AnswerToggle
key={i}
styles={styles}
name={name}
/>
);
});
I try to do it by giving reference to the Switch component. Then It says you cannot use ref without forwardRef so then I put it to the AnswerToggle component but it still giving me the error can some help me to solve this issue?. I tried lot to find out a solution to this problem. But I was unable to do so
Define the onChange handler in the parent component and pass it in as a prop. When the switch is flipped update the state in the parent accordingly and pass the new value to AnswerToggle as a prop.
// pseudo code
const [switchValues, setSwitchValues] = useState([]);
const onChange = (index, value) => setSwitchValues( ... );
answers.map((a, i) => <AnswerToggle value={switchValues[i]} onChange={newValue => onChange(i, newValue) />
This will work just fine:
const AnswerToggle = (props) => {
const {styles, name} = props;
const [toggleStatus, setToggle] = React.useState(false)
const onChange = () => setToggle(status => !status)
return (
<View style={styles.answerContentContainer}>
<View style={styles.answerTextContainer}>
<AppText styles={styles.answerText}>{name}</AppText>
</View>
<View style={styles.container}>
<Switch
trackColor={{false: '#dddddd', true: '#c1d6ee'}}
thumbColor={{false: '#ffffff', true: '#007aff'}}
ios_backgroundColor="#dddddd"
onChange={onChange}
value={toggleStatus}
style={styles.toggle}
/>
</View>
</View>
);
};
EDIT:
If you need to set the statuses of toggles into the parent component, this is my solution for you:
const AnswerToggle = (props) => {
const {styles, name, onChange, value} = props;
return (
<View style={styles.answerContentContainer}>
<View style={styles.answerTextContainer}>
<AppText styles={styles.answerText}>{name}</AppText>
</View>
<View style={styles.container}>
<Switch
trackColor={{false: '#dddddd', true: '#c1d6ee'}}
thumbColor={{false: '#ffffff', true: '#007aff'}}
ios_backgroundColor="#dddddd"
onChange={() => onChange(name)}
value={value}
style={styles.toggle}
/>
</View>
</View>
);
};
const Parent = props => {
// ... other code
// set all toggles to false
const [toggleStatuses, setToggle] = React.useState(
answers.reduce((toggles,answer) => {
toggles[answer.name] = false
return toggles
},{})
);
const onChange = name => setToggle(state => ({
...state,
[name]: !state[name],
}));
return answers.map((answer, i) => {
return (
<AnswerToggle
value={toggleStatuses[answer.name]}
onChange={onChange}
key={i}
styles={styles}
name={answer.name}
/>
);
});
}

How to use ReactNative FlatList ScrolltoIndex properly?

I'm a newbie in react native and I'm running into an issue I didn't manage to solve by myself.
So far this is how I am using it:
export default class Form extends React.Component {
state = {index: 0};
_keyExtractor = (item, index) => item.id;
myscrollToIndex = () => {
this.setState({index: ++this.state.index});
this.flatListRef.scrollToIndex({animated: true,index: this.state.index});
};
_renderItem = ({item}) => (
<View>
<Question {...item} />
<Button label='Next' onPress={this.myscrollToIndex} style={styles.buttonNext}/>
</View>
)
render() {
return (
<View style={styles.container}>
<FlatList
horizontal={false}
ref={(ref) => { this.flatListRef = ref; }}
scrollEnabled
data={this.props.form}
extraData={this.state}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem} />
</View>
);
}
}
I would like to pass the index to myscrolltoindex function but I don't manage to as this.flatListRef gets ruled out when I do that.
Have anyone run into a similar issue ?
Thank you for your time
Use index param as well. And pass the index around.
For e.g _renderItem = ({item, index}) => ( <View> <Question {...item} /> <Button label='Next' onPress={this.myscrollToIndex(index) } style={styles.buttonNext}/> </View> )

Categories

Resources