I am trying to implement a search and filtering bar with react native, but not quite sure how to work with the DataSource object. The data is in JSON form and it should do the following:
Here is a very simple example on RNPlay (Link).
How to implement the search and filtering function(s) inside react native?
import React, {Component} from 'react';
import { AppRegistry, View, ListView, Text, TextInput, StyleSheet, TouchableOpacity } from 'react-native';
const FILTERS = [
{
tag: "clever", active: false
}, {
tag: "scary", active: false
}, {
tag: "friendly", active: false
}, {
tag: "obedient", active: false
}
];
const FIELDS = [
{
title:"Dog",
subtitle: "Bulldog",
tags: [ { tag: "clever" }, { tag: "scary" } ]
}, {
title:"Cat",
subtitle:"Persian cat",
tags: [ { tag: "friendly" }, { tag: "obedient" } ]
}, {
title:"Dog",
subtitle:"Poodle",
tags: [ { tag: "obedient" } ]
}
];
class SampleApp extends Component {
constructor(props) {
super(props);
var ds = new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
});
var ds2 = new ListView.DataSource({
rowHasChanged: (row1, row2) => row1.active !== row2.active,
});
this.state = {
dataSource: ds.cloneWithRows(FIELDS),
dataSource2: ds2.cloneWithRows(FILTERS),
filters: FILTERS,
};
}
renderFilter(filter) {
return (
<TouchableOpacity onPress={this.handleClick.bind(this, filter)}>
<Text style={{fontSize: 24, backgroundColor:(filter.active)?'red':'grey', margin:5}}>{filter.tag}</Text>
</TouchableOpacity>
);
}
renderField(field) {
return (
<View style={{flexDirection:'column', borderWidth: 3, borderColor: 'yellow'}}>
<Text style={{fontSize: 24}}>{field.title}</Text>
<Text style={{fontSize: 24}}>{field.subtitle}</Text>
{field.tags.map((tagField) => {
return (
<View style={{backgroundColor:'blue'}}>
<Text style={{fontSize: 24}}>{tagField.tag}</Text>
</View>
);
})}
</View>
);
}
handleClick(filter) {
const newFilters = this.state.filters.map(a => {
let copyA = {...a};
if (copyA.tag === filter.tag) {
copyA.active = !filter.active;
}
return copyA;
});
this.setState({
dataSource2: this.state.dataSource2.cloneWithRows(newFilters),
filters: newFilters
});
}
setSearchText(event) {
let searchText = event.nativeEvent.text;
this.setState({searchText});
}
render() {
return (
<View>
<TextInput
style={styles.searchBar}
value={this.state.searchText}
onChange={this.setSearchText.bind(this)}
placeholder="Search" />
<ListView
style={{flexDirection:'row', flex:1, flexWrap:'wrap'}}
horizontal={true}
dataSource={this.state.dataSource2}
renderRow={this.renderFilter.bind(this)}
/>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderField.bind(this)}
/>
</View>
);
}
}
const styles = StyleSheet.create({
searchBar: {
marginTop: 30,
fontSize: 40,
height: 50,
flex: .1,
borderWidth: 3,
borderColor: 'red',
},
});
AppRegistry.registerComponent('SampleApp', () => SampleApp);
Here is the solution, it does both search and filtering in one function. Suggestions on how to improve it are welcome.
import React, {Component} from 'react';
import { AppRegistry, View, ListView, Text, TextInput, StyleSheet, TouchableOpacity } from 'react-native';
const FILTERS = [
{
tag: "clever", active: false
}, {
tag: "scary", active: false
}, {
tag: "friendly", active: false
}, {
tag: "obedient", active: false
}
];
const FIELDS = [
{
title:"Dog",
subtitle: "Bulldog",
tags: [ "clever", "scary" ],
active: true,
}, {
title:"Cat",
subtitle:"Persian cat",
tags: [ "friendly", "obedient" ],
active: true,
}, {
title:"Dog",
subtitle:"Poodle",
tags: [ "obedient" ],
active: true,
}
];
class SampleApp extends Component {
constructor(props) {
super(props);
var ds = new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
});
var ds2 = new ListView.DataSource({
rowHasChanged: (row1, row2) => row1.active !== row2.active,
});
this.state = {
dataSource: ds.cloneWithRows(FIELDS),
dataSource2: ds2.cloneWithRows(FILTERS),
filters: FILTERS,
};
}
renderFilter(filter) {
return (
<TouchableOpacity onPress={this.handleFilterClick.bind(this, filter)}>
<Text style={{fontSize: 24, backgroundColor:(filter.active)?'red':'grey', margin:5}}>{filter.tag}</Text>
</TouchableOpacity>
);
}
renderField(field) {
var fieldElement = <View style={{flexDirection:'column', borderWidth: 3, borderColor: 'yellow'}}>
<Text style={{fontSize: 24}}>{field.title}</Text>
<Text style={{fontSize: 24}}>{field.subtitle}</Text>
{field.tags.map((tagField) => {
return (
<View style={{backgroundColor:'blue'}}>
<Text style={{fontSize: 24}}>{tagField}</Text>
</View>
);
})}
</View>
if (field.active == true) {
return fieldElement;
} else {
return null;
}
}
handleFilterClick(filter) {
const newFilters = this.state.filters.map(f => {
let copyF = {...f};
if (copyF.tag === filter.tag) {
copyF.active = !filter.active;
}
return copyF;
});
this.setState({
dataSource2: this.state.dataSource2.cloneWithRows(newFilters),
filters: newFilters
});
this.searchAndFilter();
}
setSearchText(event) {
let searchText = event.nativeEvent.text;
this.setState({
searchText,
});
this.searchAndFilter();
}
searchAndFilter() {
//Get filtered tags
var filteredTags = [];
this.state.filters.forEach((filter) => {
if (filter.active) {
filteredTags.push(filter.tag);
}
});
const searchResults = FIELDS.map(f => {
let copyF = {...f};
//Filter
if (filteredTags.length !== intersect_safe(filteredTags, copyF.tags).length) {
copyF.active = false;
return copyF;
}
//Search
if (!this.state.searchText || this.state.searchText == '') {
copyF.active = true;
} else if (copyF.title.indexOf(this.state.searchText) != -1) {
copyF.active = true;
} else if (copyF.subtitle.indexOf(this.state.searchText) != -1) {
copyF.active = true;
} else {
copyF.active = false;
}
return copyF;
});
this.setState({
dataSource: this.state.dataSource.cloneWithRows(searchResults),
});
}
render() {
return (
<View>
<TextInput
style={styles.searchBar}
value={this.state.searchText}
onChange={this.setSearchText.bind(this)}
placeholder="Search" />
<ListView
style={{flexDirection:'row', flex:1, flexWrap:'wrap'}}
horizontal={true}
dataSource={this.state.dataSource2}
renderRow={this.renderFilter.bind(this)}
/>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderField.bind(this)}
/>
</View>
);
}
}
function intersect_safe(a, b)
{
var ai=0, bi=0;
var result = [];
while( ai < a.length && bi < b.length )
{
if (a[ai] < b[bi] ){ ai++; }
else if (a[ai] > b[bi] ){ bi++; }
else /* they're equal */
{
result.push(a[ai]);
ai++;
bi++;
}
}
return result;
}
const styles = StyleSheet.create({
searchBar: {
marginTop: 30,
fontSize: 40,
height: 50,
flex: .1,
borderWidth: 3,
borderColor: 'red',
},
});
AppRegistry.registerComponent('SampleApp', () => SampleApp);