How to change state with useState hook using a variable - react-native

I have a FlatList with about 60 items.
Each item has a small button that is clicked to display more information inside the FlatList item.
<View style={styles.infoIcon} >
<TouchableOpacity onPress={() => { toggleInfo() }} >
<Ionicons name="ios-information-circle" size={40} color={"#0ca9dd"} />
</TouchableOpacity >
</View>
{showInfo ? <View><Text>{ itemData.item.info }</Text><View> : null }
The onPress toggle function is something like:
const toggleInfo = () => {
if (showInfo === true) {
setShowInfo(false);
} else {
setShowInfo(true);
}
};
Of course, since it's a FlatList, when I click the button all of the FlatList items show their hidden contents. But I only want the info for the clicked item to show, leaving all the others unchanged.
So, I changed the onPress to take an argument:
<TouchableOpacity onPress={() => { toggleInfo(itemData.item.id) }} >
...which is fine. But now I need to target the right state array (which is the same as the id but with an "z" tacked on the end to avoid any variable conflicts):
const toggleInfo =(id) => { // id might be "ABC"
const infoState = `${id}z`;
const infoStateSET = `set${speciesState}`; // therefore: setABCz
// Now I want to target the state "ABCz" and "setABCz" useState variables
if (infoState === true) {
infoStateSET(false);
} else {
infoStateSET(true);
}
};
Now, obviously there is no literal "infoStateSET" to change the state but I do want to use the variable to target the set state function.
Is this possible?

You can just set the id in showInfo state and then check if showInfo id is the same as the itemData.item.id.
basically you would have a state like this.
const [showInfo, setShowInfo] = useState(null);
Then you would set the state like so using your toggleInfo function:
const toggleInfo = (id) => {
if (showInfo === id) {
setShowInfo(id);
} else {
setShowInfo(null);
}
}
Then in your Flatlist item:
<View style={styles.infoIcon} >
<TouchableOpacity onPress={() => { toggleInfo(itemData.item.id) }} >
<Ionicons name="ios-information-circle" size={40} color={"#0ca9dd"} />
</TouchableOpacity >
</View>
{showInfo === itemData.item.id ? <View><Text>{ itemData.item.info }</Text><View> : null }

Related

My if statement is not working in React Native

I want to build a search bar that filters a flatlist in react native. I'm doing so with a TextInput and a component SearchFilter.
In my homescreen I have this TextInput:
<TextInput
value={input}
onChangeText={(text) => setInput(text)}
style={{ fontSize: 16, marginLeft: 10 }}
placeholder="Search"
/>
And this component:
<SearchFilter data={Cars} input={input} setInput={setInput} />
In the component file I have my flatlist:
const searchFilter = (data, input, setInput) => {
console.log(input)
return (
<View>
<Text>SearchFilter</Text>
<FlatList
style={styles.list}
data={data}
renderItem={({ item }) => {
if (input === "") {
return (
<View>
<Text>test</Text>
</View>
)
}
}}
></FlatList>
</View>
);
};
When nothing is being searched I want test to be displayed.
The problem is that it shows nothing.
When I do a console.log(input) in my homescreen the console returns an emty string
but when I do a console.log(input) in my component file it returns {}. I do not know why. When I tried
if (input === " {}") {
return (
<View>
<Text>test</Text>
</View>
)
}
it also did not work.
Any asolutions?
I suppose the searchFilter is your component ?
If it is the case then you don't use the props correctly, try like this :
const SearchFilter = ({data, input, setInput}) => { ... rest of your code ... }
You can't compare a object like this, it's not the same (in the memory).
Assuming var x = {}
x == {} // false (it's the same 'content' but it's not saved at the same place in the memory
x == "{}" // false (x is a object "{}" is a string)`
Assuming var y = x
y == x // true
To compare basic object, you can use JSON.stringify() function, it's parse object to string like this : (JSON.stringify(x) == JSON.stringify({})) === true
It's explain why your condition doesn't work but I don't know why do you have a object as output (I'm not a react developer ^^)
I hope it's even be usefull for you

Toggle a button selection on same and others in React (Javascript)?

I have a form with a question and 3 answers. Only one answer can be selected so I need to toggle each answer individually AND toggle between the 3.
I put an incoming array of objects into state on component load and iterate over that data. I add in a selected key to each object of the array with boolean which will be deleted before form submit.
Right now the behavior is that I'm able to toggle the same button but clicking another does change the previous selected but have to click again to select the new current. Obviously one crucial set is missing but not sure if I should set up a new state or if it can be done with the current config. Basically, the buttons should behave like checkboxes.
The code:
const answers = ['Frequency', 'Some Times', 'Hardly Ever'] // predefined
const ChecklistModal = ({ setShowModal, checklist }: any) => {
const [updatedList, setUpdatedList] = useState(
checklist.list.map((c) => ({
...c,
selected: !!c.answer, // added key with truthy value based on already chosen from
// backend or selected in form (frontend)
})),
)
// where the magic happens
const handleAnswer = (
answer: string,
questionId: number,
listIdx: number,
) => {
const listCopy = [...updatedList]
const list = listCopy[listIdx]
list.question = questionId
list.answer = answer
list.selected = !list.selected
listCopy[listIdx] = list
setUpdatedList(listCopy)
}
return (
<Modal>
<View style={[styles.container, containerByDevice]}>
// relevant code
{checklist.list.map((li, index) => (
<View key={li.question.id}>
<Text style={styles.questionText}>{li.question.title}</Text>
<View style={styles.answerRow}>
{answers.map((answer) => (
<View
key={answer}
style={
updatedList[index].selected &&
updatedList[index].answer === answer
? styles.selectedAnswerButton
: styles.answerButton
}
>
<TouchableOpacity
onPress={() => {
handleAnswer(answer, li.question.id, index)
}}
>
<Text>{answer}</Text>
</TouchableOpacity>
</View>
))}
</View>
</View>
))}
</View>
</Modal>
)
}
export default ChecklistModal
The initial checklist data looks like this:
checklist {
concern {
title
}
list {
question {
id
title
}
answer
}
}
}
}
`
You'll need to create another state which will store the selected answer and then add modify your condition like this: -
// state to store the selected answer
const [selectedAnswer, answer]= useState('');
// modify your condition
<View
key={answer}
style={
updatedList[index].selected &&
selectedAnswer == answer && // <== Add this condition
updatedList[index].answer === answer
? styles.selectedAnswerButton
: styles.answerButton
}
>
<TouchableOpacity
onPress={() => {
handleAnswer(answer, li.question.id, index)
}}
>
<Text>{answer}</Text>
</TouchableOpacity>
</View>
Also i have created a snack for your reference :- https://snack.expo.dev/#arjunshukla97/toggleanswer

React native while adding one item id by onpress it adds all the item at once

can anyone tell me what can i do..i want to change the add button to remove button when one item added and again onpress remove ...add button will come..
function //
const [multipletest, setmultipleTest] = React.useState([]);
function addTest(crypto) {
setmultipleTest((state) => [...state, crypto.id]);}
map list //
{filterdData.map((crypto, index) => (
<View key={index.toString()}>
<TouchableOpacity onPress={() => addTest(crypto)}>
{console.log(crypto, "crypto")}
<Text> Add</Text>
</TouchableOpacity>
</View>
This logic might help
function Example() {
// default state
const [multipletest, setmultipleTest] = React.useState([]);
const add = (crypto) => {
let newData = [...multipletest];
newData.push(crypto.id);
setmultipleTest(newData);
}
const remove = (crypto) => {
let newData = [...multipletest];
newData = newData.filter((e) => e !== crypto.id);
setmultipleTest(newData);
}
// map list
return filterdData.map((crypto, index) => {
// check crypto id already exist or not
const exists = multipletest.includes(crypto.id);
return (
<View key={index.toString()}>
<TouchableOpacity
onPress={() => {
// handle onpress either add or remove as per condition
exists ? remove(crypto) : add(crypto);
}}
>
<Text>{exists ? "Remove" : "Add"}</Text>
</TouchableOpacity>
</View>
);
});
}

React native updates state "on its own"

I have two screens, one list (Flatlist) and one filter screen where I want to be able to set some filters for the list. the list screen has the states "data" and "usedFilters". When I am switching to the filters screen, the states are set as navigation parameters for react navigation and then passed via navigation.navigate, together with the onChange function, as props to the filter screen. There they are read, and the filters screen class' state is set (usually with passed filters from the list screen, if no valid filters has been passed, some are initialized).
After that the filters can be changed. If that happens, the state of the filter screen gets updated.
If then the apply button is clicked the filter screens' state is passed to the onChange function and via that back to the list screen, the onChange function updates the state "usedFilters" state of the list screen. If the cancel button is pressed null is passed to the onChange function and there is no setState call.
Setting new states for the list screen works perfectly fine. the problem is, that when i press the cancel button (or the back button automatically rendered by react navigation) the changes are kept nevertheless. That only happens if the state has been changed before. So if there has never been applied a change and hence the "usedFitlers" state of the list screen is null, this behavior does not occur. Only if I already made some changes and hence the "usedFitlers" state of the list screen has a valid value which is passed to the filters screen the cancel or go back buttons won't work as expected.
I am using expo-cli 3 and tried on my android smartphone as well as the iOS simulator. Same behavior. I looked into it with chrome dev tools as well but i simply couldn't figure out where the "usedFitlers" state was updated.
I am using react native 0.60 and react navigation 3.11.0
My best guess is that for some reason the two states share the same memory or one is pointer to the other or sth like that. (Had problems like that with python some time ago, not knowing the it uses pointers when assigning variables).
Anyone got an idea?
List Screen:
export default class ListScreen extends React.Component {
state = { data: [], usedFilters: null };
static navigationOptions = ({ navigation }) => {
let data = navigation.getParam('data')
let changefilter = navigation.getParam('changeFilter')
let currfilter = navigation.getParam('currFilter')
return {
headerTitle:
<Text style={Styles.headerTitle}>{strings('List')}</Text>,
headerRight: (
<TouchableOpacity
onPress={() => navigation.navigate('FilterScreen', {
dataset: data, onChange: changefilter, activeFilters:
currfilter })} >
<View paddingRight={16}>
<Icon name="settings" size={24} color=
{Colors.headerTintColor} />
</View>
</TouchableOpacity>
),
};
};
_onChangeFilter = (newFilter) => {
if (newFilter) {
this.setState({ usedFilters: newFilter })
this.props.navigation.setParams({ currFilter: newFilter });
} // added for debugging reasons
else {
this.forceUpdate();
let a = this.state.usedFilters;
}
}
_fetchData() {
this.setState({ data: fakedata.results },
() => this.props.navigation.setParams({ data: fakedata.results,
changeFilter: this._onChangeFilter }));
}
componentDidMount() {
this._fetchData();
}
render() {
return (
<ScrollView>
<FlatList/>
// Just data rendering, no problems here
</ScrollView>
);
}
}
Filter Screen:
export default class FilterScreen extends React.Component {
static navigationOptions = () => {
return {
headerTitle: <Text style={Styles.headerTitle}> {strings('filter')}
</Text>
};
};
state = { currentFilters: null }
_onChange = (filter, idx) => {
let tmp = this.state.currentFilters;
tmp[idx] = filter;
this.setState({ currentFilters: tmp })
}
_initFilterElems() {
const filters = this.props.navigation.getParam('activeFilters');
const dataset = this.props.navigation.getParam('dataset');
let filterA = [];
let filterB = [];
let filterC = [];
if (filters) {
// so some checks
} else {
// init filters
}
const filterElements = [filterA, filterB, filterC];
this.setState({ currentFilters: filterElements })
}
componentDidMount() {
this._initFilterElems()
}
render() {
const onChange = this.props.navigation.getParam('onChange');
return (
<ScrollView style={Styles.screenView}>
<FlatList
data={this.state.currentFilters} // Listeneinträge
keyExtractor={(item, index) => 'key' + index}
renderItem={({ item, index }) => (
<FilterCategory filter={item} name={filterNames[index]}
idx={index} onChange={this._onChange} />
)}
ItemSeparatorComponent={() => <View style=
{Styles.listSeperator} />}
/>
<View style={Layout.twoHorizontalButtons}>
<TouchableOpacity onPress={() => {
onChange(this.state.currentFilters);
this.setState({ currentFilters: null });
this.props.navigation.goBack();
}}>
<View style={Styles.smallButton}>
<Text style={Styles.buttonText}>{strings('apply')} </Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => {
onChange(null);
this.setState({ currentFilters: null });
this.props.navigation.goBack();
}}>
<View style={Styles.smallButton}>
<Text style={Styles.buttonText}>{strings('cancel')}
</Text>
</View>
</TouchableOpacity>
</View>
</ScrollView >
);
}
}
So when I press the cancel button, null is returned to the _onChangeFilter function of the list screen. This part works, and according to console.log and the debugger, the setState is not called. But if i set a breakpoint within the else part, i can see that this.state.usedFilters has changed.
Ok after a while i figured it out. The problem was that the whole filters list was always just referenced since react native (js) seems to always use references, even when changing sub-parts of the lists.
fixed that by using lodash cloneDeep.

How to update useState into TextInput

I have a simple <TextInput> in React Native and i want to print some text when value change. Its working after second character entered, but not, when user press just one char.
I try using functions and even with onChange and take from e.target.value but allways are missing one character at "search" state.
import React, { useState } from 'react';
import { TextInput, View, Text } from 'react-native';
const App = () => {
const [search, setSearch] = useState('');
const [searching, setSearching] = useState(false);
return(
<View>
<TextInput
onChangeText={(value) => {
setSearch(value)
setSearching(search=='' ? false : true)
}}
value = { search }
/>
{
searching ?
(
<Text>Searching</Text>
) : (
<Text>No searching</Text>
)
}
</View>
);
}
export default App
I expect to show "Searching" when TextBox value are not empty.
useState or setState may be asynchronous, so the first time you call setSearch(value), the search state may not be updated yet.
onChangeText={(value) => {
setSearch(value)
setSearching(search == '' ? false : true) // "search" is still previous value
}}
Instead, you can directly use value instead for checking.
onChangeText={(value) => {
setSearch(value)
setSearching(value == '' ? false : true) // value is latest
}}
You need to compare the actual value of the TextInput
Using search=='' ? false : true will check the previous state and won't change
onChangeText={(value) => {
setSearch(value)
setSearching(search=='' ? false : true)
}}
This will check for the changed value.
onChangeText={(value) => {
setSearch(value)
setSearching(value == '' ? false : true) // use value
}}
You need to use value instead of search
Just change this
setSearching(search=='' ? false : true)
to
setSearching(value=='' ? false : true)
After help of both i resolved. This is the code if help to somebody. The idea are a TextInput with a len icon on the left, and when user start to write made the call to Firebase and show a loading icon on the right (spinner).
<Icon
name='search'
size={25}
color={PRIMARY_BACKGROUND_COLOR}
style={styles.searchIcon}
/>
<TextInput
underlineColorAndroid = 'transparent'
style = { styles.textBox }
onChangeText={(value) => { onChangeText(value) }}
value = { search }
placeholder = { i18n.t('Search') }
/>
{
searching ?
(
<TouchableOpacity style = { styles.visibilityBtn }>
<Animatable.View
ref={ref => (this.AnimationRef = ref)}
animation='rotate'
easing='linear'
iterationCount='infinite'
>
<Icon
name='spinner-3'
size={20}
color={PRIMARY_BACKGROUND_COLOR}
/>
</Animatable.View>
</TouchableOpacity>
) : (
<TouchableOpacity onPress={ clearSearch } style = { styles.visibilityBtn }>
<Icon
name='close'
size={20}
color={PRIMARY_BACKGROUND_COLOR}
/>
</TouchableOpacity>
)
}
And the function
const onChangeText = value => {
setSearch(value)
setSearching(value =='' ? false : true)
Helper.getCategoriesSearch(value, (categories) => {
setSearching(false)
//props.searchResults(categories); THIS DO NOT WORK RETURN DATA TO props :(
})
}
Thanks All