I am creating a Search bar in a list view with a list of multiples types of food, this search bar can filter the results.
When I search a specific food and select this food, If I erase the search bar another food is selected.
You can see the problem in this gif:
https://giphy.com/gifs/3oKIPpagpRTfnuA9vW
How can I solve this problem?
Code:
class ListFood extends Component {
constructor() {
super();
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
ds: ds,
dataSource: ds.cloneWithRows(dataDemo),
rawData: dataDemo,
};
}
setSearchText(event) {
let searchText = event.nativeEvent.text;
this.setState({searchText});
data = dataDemo
let filteredData = this.filterNotes(searchText, data);
ds = this.state.ds;
this.setState({
dataSource: ds.cloneWithRows(filteredData),
rawData: data,
});
}
filterNotes(searchText, notes) {
let text = searchText.toLowerCase();
return notes.filter((n) => {
let note = n.toLowerCase();
return note.search(text) !== -1;
});
}
render() {
return (
<View>
<TextInput
value={this.state.searchText}
onChange={this.setSearchText.bind(this)}
/>
<ListView
dataSource={this.state.dataSource}
renderRow={(data) => <Row state={this.state.CheckBoxState} data={data} />}
/>
</View>
);
}
}
row:
class Row extends Component {
constructor(props) {
super(props);
this.state = { checked: false };
}
render() {
return (
<View´>
<CheckBox
onPress={() => this.setState({
checked: !this.state.checked
})}
checked={this.state.checked}
/>
<Text>
{ this.props.data }
</Text>
</View>
);
}
}
I have had same problem before where the row change is not correctly detected. I did a workaround by initializing the datasource every time I had new data.
So try something like
setSearchText(event) {
let searchText = event.nativeEvent.text;
this.setState({searchText});
data = dataDemo
let filteredData = this.filterNotes(searchText, data);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.setState({
dataSource: ds.cloneWithRows(filteredData),
rawData: data,
});
}
Related
I'm trying to display the time zone in another screen when an item is pressed in previous flatlist. My data is coming from autocomplete when I'm selecting it is displayed in flatlist.
<Autocomplete
autoCapitalize="none"
autoCorrect={false}
containerStyle={styles.autocompleteContainer}
data={autotime.length === 1 && comp(query, autotime[0].name) ? [] : autotime}
defaultValue={this.state.timeZone}
onChangeText={text => this.setState({ query: text })}
placeholder="Enter Location"
renderItem={({ name, release_date }) => (
<TouchableOpacity onPress={() => this.setState({ query: name,timezoneArray:autotime[0].timezones })}>
<Text style={styles.itemText}>
{name}
</Text>
</TouchableOpacity>
)}
/>
<View style={styles.descriptionContainer}>
{autotime.length > 0 ? (
<FlatList
style={{flex:1}}
data={this.state.timezoneArray}
renderItem={({ item }) => <TimeZoneItem text={item} />}
/>
) : (
<Text style={styles.infoText}>Enter Location</Text>
)}
I want that when I press the items of flatlist it is displayed on another page.
Picture below shows what I have made:
My Data base helper class is:
export const SaveItem = (key, value) => {
AsyncStorage.setItem(key, value);
};
export const ReadItem = async (key) => {
try {
var result = await AsyncStorage.getItem(key);
return result;
} catch (e) {
return e;
}
};
export function MultiRead(key, onResponse, onFailure) {
try {
AsyncStorage.multiGet(key).then(
(values) => {
let responseMap = new Map();
values.map((result, i, data) => {
let key = data[i][0];
let value = data[i][1];
responseMap.set(key, value);
});
onResponse(responseMap)
});
} catch (error) {
onFailure(error);
}
};
export async function DeleteItem(key) {
try {
await AsyncStorage.removeItem(key);
return true;
}
catch (exception) {
return false;
}
}
and here i have added my code to save
handleTimezone = (text) => {
this.setState({ TimeZoneItem: text })
}
newData.TimeZoneItem = this.state.TimeZoneItem
this.setState({
TimeZoneItem: '',
})
ReadItem('timeData').then((result) => {
let temp = []
if (result != null) {
temp = JSON.parse(result)
} else {
temp = []
}
temp.push(newData)
SaveItem('timeData', JSON.stringify(temp))
console.log(`New Data: ${JSON.stringify(temp)}`)
}).catch((e) => {
})
}
<FlatList
style={{flex:1}}
data={this.state.timezoneArray}
renderItem={({ item }) => (
<TouchableOpacity>
<TimeZoneItem text={item} onPress={() => this.props.onPress()}
value={this.state.TimeZoneItem}
/>
</TouchableOpacity>)}
You'll need an array to saved all the saved items.
for example
state = {
saved: []
};
On press the time zone item, get the values from state, add the new item to the array and save the array to async storage using JSON.stringify()
onSave = item => {
const { saved } = this.state;
const newItems = [...saved, item];
this.setState({
saved: newItems
});
const items = JSON.stringify(newItems);
SaveItem("saved", items)
.then(res => {
console.warn("saved", res);
})
.catch(e => console.warn(e));
};
Then in your other screen get the items in using your ReadItem function like.
state = {
saved: []
};
componentDidMount = () => {
ReadItem("saved")
.then(res => {
if (res) {
const saved = JSON.parse(res);
this.setState({
saved: saved
});
}
})
.catch(e => console.warn(e));
};
Working Demo
I want to select only one checkbox, not multiple.
If i select two checkboxes one by one the previously selected checkbox should be unselected.
In my below code i can select multiple checkboxes.
import React ,{Component} from "react";
import CircleCheckBox, {LABEL_POSITION} from "react-native-circle-checkbox";
class Select_Delivery_Option extends React.Component {
constructor(props) {
super(props);
const ds = new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
});
this.state = {
check_data:[],
dataSource: ds.cloneWithRows([]),
checked:false,
isLoading:false,
};
}
//I had call The componentDidMount for json Data here and bind it in Data source;
render() {
return ();
}
_renderRow(rowData: string, sectionID: number, rowID: number) {
return (
<View style={{ flex:1,flexDirection:'column',backgroundColor:'#FFF'}}>
<View style={{ flex:1,flexDirection:'row',backgroundColor:'#FFF'}}>
<View style={{flexDirection:'column',margin:10}}>
{rowData.adbHomeAddress}
<CircleCheckBox
checked={rowData.checked}
onToggle={()=>this._onPressRow(rowID, rowData,rowData.checked)}
labelPosition={LABEL_POSITION.LEFT}
label={rowData.Address1 +" ,\n "+ rowData.Address2 +",\n"+rowData.ctiName+", "+rowData.staName+", "+rowData.ctrName+","+rowData.adbZip+"."}
innerColor="#C72128"
outerColor="#C72128"
styleLabel={{color:'#000',marginLeft:10}}
/>
</View>
</View>
</View>
);
}
_onPressRow = (rowID,rowData,checked) => {
const {check_data,filter} = this.state;
console.log('rowdata',rowData);
console.log('rowid',rowID);
console.log('checked',checked);
rowData.checked = !rowData.checked;
var dataClone = this.state.check_data;
dataClone[rowID] = rowData;
this.setState({check_data: dataClone });
}
}
Link to the CircleCheckBox component used: https://github.com/paramoshkinandrew/ReactNativeCircleCheckbox
I had the same requirement and wasted hours looking for solution. Eventually, I was able to resolve the problem on my own.
Posting my answer below, l have used hooks in the example, let me know if someone wants a class-based solution.
const checkboxComponent = () => {
const [checkboxValue, setCheckboxValue] = React.useState([
{ label: 'Customer', value: 'customer', checked: false },
{ label: 'Merchant', value: 'merchant', checked: false },
{ label: 'None', value: 'none', checked: false },
])
const checkboxHandler = (value, index) => {
const newValue = checkboxValue.map((checkbox, i) => {
if (i !== index)
return {
...checkbox,
checked: false,
}
if (i === index) {
const item = {
...checkbox,
checked: !checkbox.checked,
}
return item
}
return checkbox
})
setCheckboxValue(newValue)
}
return (
<View>
{checkboxValue.map((checkbox, i) => (
<View style={styles.checkboxContainer} key={i}>
<CheckBox
value={checkbox.checked}
onValueChange={(value) => checkboxHandler(value, i)}
/>
<Text style={styles.label}>{checkbox.label}</Text>
</View>
))}
</View>
)
}
export default checkboxComponent
I suggest you to use FlatList instead of ListView it's more advance and easy to use component.
For your issue please create a state checkedItem: -1 and directly assign id of your item you check last then just add a check to your CircleCheckBox item. something like below code.
<CircleCheckBox
checked={rowData.id === this.state.checkedItem}
onToggle={(rowID)=> this.setState({ checkedItem: rowID})}
labelPosition={LABEL_POSITION.LEFT}
label={rowData.Address1 +" ,\n "+ rowData.Address2 +",\n"+rowData.ctiName+", "+rowData.staName+", "+rowData.ctrName+","+rowData.adbZip+"."}
innerColor="#C72128"
outerColor="#C72128"
styleLabel={{color:'#000',marginLeft:10}}
/>
Let me know if any query.
Is there any way to access a row using key, set using keyExtractor in FlatList.
I using FlatList inorder to populate by data, i need to get a row separately using it's key, inorder to update that row without re-render the entire view.
on componentWillMount i populated datalist array using an api call.
dummy array look this
[{id:"Usr01",title:"Name1"},{id:"Usr02",title:"Name2"},...]
while press on any row i get it's id, i need to access that row using it's key.
let dataitem = this.state.datalist[id];
while i console dataitem i get undefined
i set id as the key in keyExtractor, is there any way to do the same.
My code look like this
FlatListScreen.js
export default class FlatListScreen extends Component {
constructor(props)
{
super(props);
this.state={
datalist: [],
}
}
componentWillMount() {
ApiHandler.getlistitem('All').then(response =>{
this.setState({datalist: response});
});
}
_keyExtractor = (item, index) => item.id;
_onPressItem = (id) => {
let dataitem = this.state.datalist[id];
const { name } = dataitem
const newPost = {
...dataitem,
name: name+"01"
}
this.setState({
datalist: {
...this.state.datalist,
[id]: newPost
}
})
};
_renderItem ({ item }) {
return (
<MyListItem
id={item.id}
onPressItem={this._onPressItem}
title={item.title}
/>
)
}
render() {
return (
<FlatList
data={this.state.datalist}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
/>
);
}
}
}
MyListItem.js
export default class MyListItem extends Component {
constructor(props) {
super(props)
this.state = {
title: '',
id: ''
}
}
componentWillMount() {
const { title, id } = this.props
this.setState({ title, id })
}
componentWillReceiveProps(nextProps) {
const { title, id } = nextProps
this.setState({ title, id })
}
shouldComponentUpdate(nextProps, nextState) {
const { title} = nextState
const { title: oldTitle } = this.state
return title !== oldTitle
}
render() {
return (
<View>
<TouchableOpacity onPress={() =>this.props.onPressItem({id:this.state.id})}>
<View>
<Text>
{this.props.title}
</Text>
</View>
</TouchableOpacity>
</View>
);
}
}
I think changing
onPressItem({id:this.state.id});
to
onPressItem(this.state.id); in your child component
OR
_onPressItem = (id) => { }
to
_onPressItem = ({id}) => { }
in your parent component will solve the issue.
As you are sending it as an object from child to parent and you can access it like this also
let dataitem = this.state.datalist[id.id];
When swiping the row in the SwipeableListView I want to delete the rowitem and re-render the list.
What is now happening is that always the last item in the list is removed, not the item that is swiped.
Any ideas what is wrong?
export default class SwipeList extends Component {
constructor(props) {
super(props);
let ds = SwipeableListView.getNewDataSource();
this.favourites = []
this.state = {
ds:[],
dataSource:ds,
isLoading:true,
closeRow:false,
};
}
componentWillMount () {
store.get('KEY_FAV').then(value => {
typeof(value) === 'object'
? this.favourites = Object.keys(mockdata.favourite)
: this.favourites = JSON.parse(value)
this.setState({
dataSource: this.state.dataSource.cloneWithRowsAndSections(this.genData(this.favourites
)),
isLoading:false
})
})
}
genData = (list) => {
let dataBlob = []
for(let i = 0; i <list.length; i++) {
dataBlob.push({id:list[i], name:list[list[i]]})
}
return [dataBlob, []]
}
Till here it is okay, the SwipeableList is loaded with all RowItems.
But in the below handleSwipeAction() while setting new state for dataSource, the list will only delete the last item, not the selected.
handleSwipeAction = (rowData, SectionID, rowID) => {
AlertIOS.alert('Remove ' + rowData.name + ' \nfrom Favourites?', null,
[
{text:'Cancel', onPress: () => {this.setState({closeRow:true})}, style:'cancel'},
{text:'OK', onPress: () => {
this.favourites.slice()
this.favourites.splice(rowID, 1)
this.setState({
closeRow:true,
})
this.setState({//I THINK HERE IS THE PROBLEM
dataSource:this.state.dataSource.cloneWithRowsAndSections(this.genData(this.favourites))
})
store.set('KEY_FAV', this.favourites)
}}
])
}
onSwipe = (rowData, SectionID, rowID) => {
return (
<View style={styles.actionsContainer}>
<TouchableHighlight
onPress={() => this.handleSwipeAction(rowData, SectionID, rowID)}>
<Text style={styles.actionsItem}>Remove</Text>
</TouchableHighlight>
</View>
);
};
and the render function
render() {
if(this.state.isLoading) return null
return (
<View style={styles.container}>
<SwipeableListView
bounceFirstRowOnMount
enableEmptySections={true}
dataSource={this.state.dataSource}
maxSwipeDistance={this.props.swipeDistance}
renderRow={(item) => this.renderItem(item)}
renderQuickActions={this.onSwipe}
renderSeparator={this.renderSeperator}
doCloseRow={this.state.closeRow}
/>
</View>
);
}
when you are done slicing, I believe if you do:
let ds = SwipeableListView.getNewDataSource(); all over again, and then
this.setState({ dataSource: ds.cloneWithRowsAndSections(this.genData(this.favourites)) })
It should work. For a reason that I still don't get. Also I don't know why you do two setState() in your function. One is enough no?
So this should work:
handleSwipeAction = (rowData, SectionID, rowID) => {
AlertIOS.alert('Remove ' + rowData.name + ' \nfrom Favourites?', null,
[
{text:'Cancel', onPress: () => {this.setState({closeRow:true})}, style:'cancel'},
{text:'OK', onPress: () => {
this.favourites.slice()
this.favourites.splice(rowID, 1)
let ds = SwipeableListView.getNewDataSource(); // add this
this.setState({ dataSource: ds.cloneWithRowsAndSections(this.genData(this.favourites)), closeRow:true })
store.set('KEY_FAV', this.favourites) }} ])
}
I'm trying to force a ListView to rerender, only the changed rows.
The documentation suggests rowHasChanged function. I'm not sure if I'm using it correctly.
here's the redacted code for reference, which doesn't work for me:
'use strict'
import React from 'react-native';
import {
ListView,
View,
TouchableOpacity,
Component
}
from 'react-native';
export default class Follows extends Component {
constructor() {
super()
this.state = {
apiData: [],
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1.id !== row2.id || row1.following !== row2.following
})
};
}
_fetchData() {
fetch(some_url)
.then((response) => response.json())
.then((responseData) => {
this.state.apiData = responseData.data;
this.setState({
dataSource: this.state.dataSource.cloneWithRows(this.state.apiData),
});
})
.catch((error) => {
callOnFetchError(error, some_url);
}).done();
})
}
componentDidMount() {
this._fetchData()
}
_follow(id, shouldFollow) {
const refreshedData = this.state.apiData.map(user => {
if (user.id === id)
user.following = shouldFollow ? 1 : 0;
return user;
})
this.setState({
dataSource: this.state.dataSource.cloneWithRows(refreshedData),
})
}
_renderRow() {
return (
<TouchableOpacity
onPress = {this._follow.bind(this, 37, true}>
/* some view */
</TouchableOpacity>
)
}
render () {
return (
<View style={styles.container}>
<ListView
style={styles.listView}
dataSource={ this.state.dataSource }
renderRow={this._renderRow()}
</ListView >
< /View>
)
}
}
Try the below setup. There are two things I think that are making the functionality not work for you:
1 - The way you are referring to your dataSource as this.state.dataSource.cloneWithRows. Instead, try setting a variable like below:
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
Then using it like this:
this.setState({
dataSource: ds.cloneWithRows(responseData.data),
});
2 - Instead of this:
this.state.apiData = responseData.data;
Try this:
this.setState({
apiData: responseData.data
})
Overall, I've changed the original code to the below, which I think should work:
'use strict'
import React from 'react-native';
import {
ListView,
View,
TouchableOpacity,
Component
}
from 'react-native';
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
export default class Follows extends Component {
constructor() {
super()
this.state = {
apiData: [],
dataSource: ds.cloneWithRows([])
};
}
_fetchData() {
fetch(some_url)
.then((response) => response.json())
.then((responseData) => {
this.setState({
apiData: responseData.data,
dataSource: ds.cloneWithRows(responseData.data),
});
})
.catch((error) => {
callOnFetchError(error, some_url);
}).done();
})
}
componentDidMount() {
this._fetchData()
}
_follow(id, shouldFollow) {
const refreshedData = this.state.apiData.map(user => {
if (user.id === id)
user.following = shouldFollow ? 1 : 0;
return user;
})
this.setState({
dataSource: ds.cloneWithRows(refreshedData),
})
}
_renderRow() {
return (
<TouchableOpacity
onPress = {this._follow.bind(this, 37, true}>
/* some view */
</TouchableOpacity>
)
}
render () {
return (
<View style={styles.container}>
<ListView
style={styles.listView}
dataSource={ this.state.dataSource }
renderRow={this._renderRow()}
</ListView >
< /View>
)
}
}