I have my Main function in one file:
import Search from '../Components/Header';
function Main() {
return (
<View>
<Search />
<FlatList
data={this.state.data}
renderItem={renderItem}
keyExtractor={(item) => item.id}
style={{borderColor: 'black', borderWidth: 1, flexWrap: 'wrap'}}
/>
</View>
And Search class in another file:
const DATA = [
{
id: "1",
title: "Data",
}
];
const Item = ({ title }) => {
return (
<View>
<Text>{title}</Text>
</View>
);
};
const renderItem = ({ item }) => <Item title={item.title} />;
export default class Search extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
data: DATA,
error: null,
searchValue: "",
};
this.arrayholder = DATA;
};
searchFunction = (text) => {
const updatedData = this.arrayholder.filter((item) => {
const item_data = `${item.title.toUpperCase()})`;
const text_data = text.toUpperCase();
return item_data.indexOf(text_data) > -1;
});
this.setState({ data: updatedData, searchValue: text });
};
render() {
return (
<View style={Headerstyles.rectangle}>
<SearchBar
value={this.state.searchValue}
onChangeText={(text) => this.searchFunction(text)}
/>
</View>
);
}
}
So as I understand I should pass props from Flatlist to Search class, but I get an error TypeError: Cannot read property 'data' of undefined. I think it's not only about data and also renderItem and keyExtractor.
How can I do this?
The component Main does not contain a state called data. This state is defined in Search. I would create the state inside Main and pass the setter function to Search.
function Main() {
const [data, setData] = React.useState(DATA);
return (
<View>
<Search setData={setData} />
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.id}
style={{borderColor: 'black', borderWidth: 1, flexWrap: 'wrap'}}
/>
</View>
)
}
Then, use the new props in Search as follows.
export default class Search extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
error: null,
searchValue: "",
};
this.arrayholder = DATA;
};
searchFunction = (text) => {
const updatedData = this.arrayholder.filter((item) => {
const item_data = `${item.title.toUpperCase()})`;
const text_data = text.toUpperCase();
return item_data.indexOf(text_data) > -1;
});
this.setState({ searchValue: text });
this.props.setData(updatedData);
};
render() {
return (
<View style={Headerstyles.rectangle}>
<SearchBar
value={this.state.searchValue}
onChangeText={(text) => this.searchFunction(text)}
/>
</View>
);
}
}
Related
I used this tutorial but it didn't work (if you are interested, check out my last post). Any suggestions to make a working search for a flatlist?
I have a list of 100 things and just by inserting the name in a search bar, the flatlist should update with the results.
Try using react-native-searchable-flatlist
import React, { Component } from 'react';
import { View, Text, FlatList, ActivityIndicator } from 'react-native';
import { ListItem, SearchBar } from 'react-native-elements';
class FlatListDemo extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
error: null,
};
this.arrayholder = [];
}
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const url = `https://randomuser.me/api/?&results=20`;
this.setState({ loading: true });
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
data: res.results,
error: res.error || null,
loading: false,
});
this.arrayholder = res.results;
})
.catch(error => {
this.setState({ error, loading: false });
});
};
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: '86%',
backgroundColor: '#CED0CE',
marginLeft: '14%',
}}
/>
);
};
searchFilterFunction = text => {
this.setState({
value: text,
});
const newData = this.arrayholder.filter(item => {
const itemData = `${item.name.title.toUpperCase()} ${item.name.first.toUpperCase()} ${item.name.last.toUpperCase()}`;
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
this.setState({
data: newData,
});
};
renderHeader = () => {
return (
<SearchBar
placeholder="Type Here..."
lightTheme
round
onChangeText={text => this.searchFilterFunction(text)}
autoCorrect={false}
value={this.state.value}
/>
);
};
render() {
if (this.state.loading) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<ActivityIndicator />
</View>
);
}
return (
<View style={{ flex: 1 }}>
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
leftAvatar={{ source: { uri: item.picture.thumbnail } }}
title={`${item.name.first} ${item.name.last}`}
subtitle={item.email}
/>
)}
keyExtractor={item => item.email}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
/>
</View>
);
}
}
export default FlatListDemo;
I am trying to create a search bar to filter data for flatlist. In the code below, I have created a list of name and pass the name list into flatlist. Then, I would like to pass in the input from search bar to
searchFilterFunction to filter the data. The issue is when I enter the page, I have encountered error "Undefined is not an object (evaluating this.state.text). Would like to know the cause and solution for this case.
const DATA = [
{
name: 'Name 1',
}, {
name: 'Name 2',
}, {
name: 'Name 3',
}, {
name: 'Name 4',
}, {
name: 'Name 5',
},
];
export default class NameListView extends React.Component<any, any> {
constructor(props) {
super(props);
this.state = {
loading: false,
text: '',
};
this.searchFilterFunction = this.searchFilterFunction.bind(this);
}
searchFilterFunction (text) {
const newData = DATA.filter(function(item) {
const itemData = `${item.name}`;
const textData = text;
return itemData.indexOf(textData) > -1;
});
this.setState({
text: newData
});
};
renderSeparator(){
return <View style={styles.separator}/>
}
renderHeader() {
return (
<SearchBar
placeholder="Choose Recipient"
lightTheme
round
onChangeText={text => this.searchFilterFunction(text)}
autoCorrect={false}
value={this.state.text}
/>
);
};
render() {
return (
<SafeAreaView>
<View>
<FlatList style={styles.flatList}
data={DATA}
renderItem={({ item }) =>
<ListItem
title={item.name}
chevron={{color : 'black'}}
leftIcon={<FontAwesome name={'user-circle'} size={25} color=
{'#E3E3E3'}/>}
/>
}
keyExtractor={item => item.name}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
stickyHeaderIndices={[0]}
/>
</View>
</SafeAreaView>
)
}
}
Since you are accessing this inside renderHeader it needs to either be bound or called with the correct scope.
You can change the call to something like:
ListHeaderComponent={this.renderHeader.bind(this)}
Or something like:
ListHeaderComponent={() => this.renderHeader()}
I created a SectionList and tried to implement a search filter for my SectionList. But my output got an error. I took a screenshot of it below. I don't know what's wrong.
This is my component.
export default class Cluster1 extends Component{
constructor(props){
super(props)
this.state = {
dataToShow: '',
search: false
}
}
searchUpdated = (term) => {
let matchedItemsArray = []
if(term === ''){
this.setState({search: false, dataToShow: ClusterData})
}else{
this.state.dataToShow.map((item) => {
if(item.title.includes(term)){
matchedItemsArray.push(item)
}
})
this.setState({search: true, dataToShow: matchedItemsArray})
}
}
searchUpdated = (input) => {
let userInput =[]
if(input === ''){
this.setState({search: false})
userInput = ''
}else{
this.setState({search: true})
}
}
render(){
return(
<View style={styles.container}>
<TextInput
onChangeText={(term) => { this.searchUpdated(text) }}
style={styles.searchInput}
placeholder="Type a mood to search"
/>
<SectionList
renderItem = {({item, index}) =>
<SectionListItem item = {item} index = {index}/>}
renderSectionHeader = {({section}) =>
<SectionHeader
sections={this.searchUpdated()}
keyExtractor = {(item) => item.name}/>}>
</SectionList> </View>
);
}}
class SectionHeader extends Component {
render() {
return (
<View style={styles.header}>
<Text style={styles.headertext}>
{this.props.section.title}
</Text>
<TouchableOpacity onPress={ () => Actions.SongList({ section: this.props.section}) }>
<Text style ={styles.Play}> Play
</Text>
</TouchableOpacity>
</View>
); }
}
class SectionListItem extends Component{
render(){
return(
<View>
<Text style={styles.moodname}>{this.props.item.name}</Text>
</View>
);
}}
This is my data
const ClusterData = [
{ title: 'Cluster1',
data:
[
{name: 'passionate'},{name: 'rousing'},{name: 'confident'},
{name: 'boisterous'},{name: 'rowdy'}],
},
{
title: 'Cluster2',
data:
[
{name: 'rollicking'},{name: 'cheerful'{name: 'fun'},{name: 'sweet'},
{name: 'amiable'},{name: 'natured'}],
Here is a simple search filter:
I added a search state to help determine whether the user is currently searching or not.
constructor(props){
super(props)
this.state = {
dataToShow: '',
search: false
}
}
Then, we create the search function.
searchUpdated = (term) => {
let matchedItemsArray = []
if(term === ''){
this.setState({search: false, dataToShow: ClusterData})
}else{
this.state.dataToShow.map((item) => {
if(item.title.includes(term)){
matchedItemsArray.push(item)
}
})
this.setState({search: true, dataToShow: matchedItemsArray})
}
}
When the input is '', the search state is false. Otherwise, the function will map through the dataToShow array to find if any section titles include the user's input.
Alternatively, I like to use a lodash filter instead for it's simplicity.
First, we declare a constant called userInput:
let userInput
Then, we create a function to determine whether the userInput is empty or not to set the search state. (Remember to keep this.state.search that we created in the first place)
searchUpdated = (input) => {
if(input === ''){
this.setState({search: false})
userInput = ''
}else{
this.setState({search: true})
}
}
Finally, in our SectionList we use the lodash filter to help filter for the right section header names:
<SectionList
renderItem = {({item, index}) =>
<SectionListItem item = {item} index = {index}/>}
renderSectionHeader = {({section}) =>
<SectionHeader
section = {section}
sections = {
this.state.search ?
_.filter(this.state.dataToShow, function(item){
return item.title.includes(userInput)})
: this.state.dataToShow}
keyExtractor = {(item) => item.name}/>}>
</SectionList>
The entire component
import React from 'react'
import { View, Text, SectionList, TouchableOpacity, TextInput } from 'react-native'
const ClusterData = [
{title: 'Cluster1', data: [{name: 'passionate'},{name: 'rousing'},{name: 'confident'},{name: 'boisterous'},{name: 'rowdy'}]},
{title: 'Cluster2', data: [{name: 'rollicking'},{name: 'cheerful'},{name: 'fun'},{name: 'sweet'},{name: 'amiable'},{name: 'natured'}]}
]
let userInput = ''
export default class TempScreen extends React.Component {
constructor(props){
super(props)
this.state = {
search: false,
dataToShow: []
}
}
componentWillMount(){
this.setState({dataToShow: ClusterData})
}
searchUpdated = (term) => {
let matchedItemsArray = []
if(term === ''){
this.setState({search: false, dataToShow: ClusterData})
}else{
this.setState({search:true, dataToShow: ClusterData}, function(){
this.state.dataToShow.map((item) => {
if(item.title.includes(term)){
matchedItemsArray.push(item)
}
})
this.setState({dataToShow:matchedItemsArray})
})
}
}
render () {
return (
<View>
<TextInput
onChangeText={(term) => {this.searchUpdated(term)}}
style={styles.searchInput}
placeholder="Type a mood to search"/>
<SectionList
renderItem={({item}) => <SectionListItem itemName = {item.name}/>}
renderSectionHeader={({section}) => <SectionHeader sectionTitle = {section.title}/>}
sections={this.state.dataToShow}
/>
</View>
)
}
}
class SectionHeader extends React.Component{
render(){
return(
<View>
<Text>{this.props.sectionTitle}</Text>
<TouchableOpacity>
<Text>Play</Text>
</TouchableOpacity>
</View>
)
}
}
class SectionListItem extends React.Component{
render(){
return(
<View>
<Text>{this.props.itemName}</Text>
</View>
)
}
}
I'm trying to make an auto-refreshing flatlist every time the user types something, like Instagram’s search does.
The compiler keeps complaining about a missing variable called search.
import React, { Component } from "react";
import { View, Text, FlatList, ActivityIndicator } from "react-native";
import { List, ListItem, SearchBar } from "react-native-elements";
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
page: 1,
error: null,
search: '',
};
}
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const { page, search } = this.state;
const url = `https://pacific-basin-57759.herokuapp.com/api/account/users?page=${page}&search=${search}`;
this.setState({ loading: true });
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
data: page === 1 ? res.results : [...this.state.data, ...res.results],
error: res.error || null,
loading: false,
search: this.state.search
})
})
.catch(error => {
this.setState({ error, loading: false });
});
};
handleLoadMore = () => {
this.setState(
{
page: this.state.page + 1
},
() => {
this.makeRemoteRequest();
}
);
};
handleSearch = () => {
this.setState(
{
search: this.state.search
},
() => {
this.makeRemoteRequest();
}
);
}
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: "86%",
backgroundColor: "#CED0CE",
marginLeft: "14%"
}}
/>
);
};
renderHeader = () => {
return <SearchBar placeholder="Type Here..." lightTheme round onChangeText={(text) => this.setState({search:text})} />;
};
renderFooter = () => {
if (!this.state.loading) return null;
return (
<View
style={{
paddingVertical: 20,
borderTopWidth: 1,
borderColor: "#CED0CE"
}}
>
<ActivityIndicator animating size="large" />
</View>
);
};
render() {
return (
<List containerStyle={{ borderTopWidth: 0, borderBottomWidth: 0 }}>
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
//roundAvatar
title={`${item.first_name} ${item.last_name}`}
subtitle={item.username}
//avatar={{ uri: item.picture.thumbnail }}
containerStyle={{ borderBottomWidth: 0 }}
/>
)}
keyExtractor={item => item.id}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={0.5}
/>
</List>
);
}
}`
I've looked at the Fetch API documentation at MDN and it doesn't really give any useful info (it's all over the place).
I need to call SearchScreen class method from a React Navigation Header.
The Navigator look like this:
Search: {
screen: SearchScreen,
path: 'search/:query',
navigationOptions: {
title: 'Search',
header: {
right: (
<MaterialCommunityIcons
name="filter"
onPress={() => {
console.log(this);
}}
style={{marginRight: 15, color: 'white'}}
size={24}
/>
),
},
}
}
I've made it work by doing:
// declare static navigationOptions in the Component
static navigationOptions = {
title: 'Title',
header: ({ state }) => ({
right: (
<MaterialCommunityIcons
name="filter"
onPress={state.params.handleFilterPress}
style={{marginRight: 15, color: 'white'}}
size={24}
/>
),
}),
}
_handleFilterPress() {
// do something
}
componentDidMount() {
// set handler method with setParams
this.props.navigation.setParams({
handleFilterPress: this._handleFilterPress.bind(this)
});
}
I've resolved the issue the following way:
static navigationOptions = ({ navigation }) => {
return {
headerRight: () => (
<TouchableOpacity
onPress={navigation.getParam('onPressSyncButton')}>
<Text>Sync</Text>
</TouchableOpacity>
),
};
};
componentDidMount() {
this.props.navigation.setParams({ onPressSyncButton: this._onPressSyncButton });
}
_onPressSyncButton = () => {
console.log("function called");
}
Hooks solution with FunctionComponent, useState and useEffect
Ref the official docs (https://reactnavigation.org/docs/en/header-buttons.html#header-interaction-with-its-screen-component) it is done by:
class HomeScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
return {
headerTitle: <LogoTitle />,
headerRight: (
<Button
onPress={navigation.getParam('increaseCount')}
title="+1"
color="#fff"
/>
),
};
};
componentDidMount() {
this.props.navigation.setParams({ increaseCount: this._increaseCount });
}
state = {
count: 0,
};
_increaseCount = () => {
this.setState({ count: this.state.count + 1 });
};
/* later in the render function we display the count */
}
However I could not get this to work when working with the hooks api. My state variables were always undefined, but after I thought about how the hooks api is implemented it all made sense, so the solution was to update the navigation param every time a significant state variable was changed:
const [count, setCount] = useState(0);
useEffect(() => {
props.navigation.setParams({ increaseCount });
}, [count]);
const increaseCount = () => setCount(count + 1);
I came across same issue and able to resolve the issue from below links.
class MyScreen extends React.Component {
static navigationOptions = {
header: {
right: <Button title={"Save"} onPress={() => this.saveDetails()} />
}
};
saveDetails() {
alert('Save Details');
}
render() {
return (
<View />
);
}
}
Source: react-native issues 145
Below is my code
import React, { Component } from "react";
import {
Container,
Header,
Item,
Input,
Icon,
Button,
Text,
Left,
Body,
Right,
Content,
Spinner,
List,
ListItem
} from "native-base";
import { View, Image, StyleSheet, Keyboard } from "react-native";
import { connect } from "react-redux";
import {
onClear,
onSearchTextChanged,
searchForProfiles
} from "../../actions/searchActions";
class SearchBar extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Header searchBar rounded>
<Button
iconLeft
style={{ paddingLeft: 0 }}
light
onPress={this.props.onBackPress}
>
<Icon style={{ marginLeft: 0, fontSize: 35 }} name="arrow-back" />
</Button>
<Item>
<Icon name="ios-search" />
<Input
placeholder="Search"
onChangeText={this.props.onChangeText}
value={this.props.searchText}
/>
<Button small transparent onPress={this.props.onClear}>
<Icon name="ios-close" />
</Button>
</Item>
<Button transparent onPress={this.props.onSearch}>
<Text>Search</Text>
</Button>
</Header>
);
}
}
class SearchWorld extends Component {
static navigationOptions = ({ navigation }) => ({
left: null,
header: () => {
const { state } = navigation;
return (
<SearchBar
onBackPress={() => navigation.goBack()}
onChangeText={text => {
state.params.onChangeText(text);
}}
onSearch={state.params.onSearch}
onClear={state.params.onClear}
searchText={state.params.searchText}
/>
);
}
});
onChangeText = text => {
this.props.navigation.setParams({
...this.props.navigation.state,
searchText: text
});
this.props.onSearchTextChanged(text);
};
onSearch = () => {
Keyboard.dismiss();
const profile = { search: "test" };
const token = this.props.token;
this.props.searchForProfiles(token, profile);
};
onClear = () => {
this.props.onClear();
this.props.navigation.setParams({
...this.props.navigation.state,
searchText: ""
});
};
componentDidMount() {
this.props.navigation.setParams({
onChangeText: this.onChangeText,
onSearch: this.onSearch,
onClear: this.onClear,
searchText: this.props.searchText
});
}
render() {
const { searchResults } = this.props;
let items = [];
if(searchResults && searchResults.data && searchResults.data.length > 0) {
items = [...searchResults.data];
}
return this.props.loading ? (
<Container style={{ alignItems: "center", justifyContent: "center" }}>
<Spinner color="#FE6320" />
</Container>
) : (
<Container>
<Content>
<List
style={{}}
dataArray={items}
renderRow={item => (
<ListItem style={{ marginLeft: 0}}>
<Text style={{paddingLeft: 10}}>{item.password}</Text>
</ListItem>
)}
/>
</Content>
</Container>
);
}
}
const mapStateToProps = state => {
const { token } = state.user;
const { searchText, searchResults, error, loading } = state.searchReaducer;
return {
token,
searchText,
searchResults,
error,
loading
};
};
export default connect(mapStateToProps, {
onClear,
onSearchTextChanged,
searchForProfiles
})(SearchWorld);
static navigationOptions = ({navigation}) => {
return {
headerTitle: () => <HeaderTitle />,
headerRight: () => (<Button iconLeft transparent small onPress = {navigation.getParam('onPressSyncButton')}>
<Icon style ={{color:'white', fontWeight:'bold'}} name='md-save' size = {32} />
<Text style ={{color:'white', fontWeight:'bold'}}>save</Text>
</Button>),
headerTintColor:'black',
headerStyle: {
backgroundColor: '#6200EE'
},
}
};
this.props.navigation.setParams({ onPressSyncButton: this.updateUserProfile });