Reputation: 91
can someone please help me troubleshoot?
I am trying to search through a local JSON file and return the relevant data. My JSON file contains a restaurant name and restaurant type.
I don't understand where the issue may be coming from. When I comment out ListHeadComponent={this.renderHeader}, there is no error; however, nothing will display if I comment that out.
The issue I am getting is:
Invariant Violation: Invariant Violation: Tried to get frame for out of range index NaN
This error is located at:
in VirtualizedList (at FlatList.js:662)
in FlatList (at SearchScreen.js:75)
in RCTView (at View.js:44)
in SearchScreen (created by SceneView)
in SceneView (at StackViewLayout.js:795)
in RCTView (at View.js:44)
in AnimatedComponent (at StackViewCard.js:69)
in RCTView (at View.js:44)
in AnimatedComponent (at screens.native.js:59)
in Screen (at StackViewCard.js:57)
in Card (at createPointerEventsContainer.js:27)
in Container (at StackViewLayout.js:860)
in RCTView (at View.js:44)
in ScreenContainer (at StackViewLayout.js:311)
in RCTView (at View.js:44)
in AnimatedComponent (at StackViewLayout.js:307)
in Handler (at StackViewLayout.js:300)
in StackViewLayout (at withOrientation.js:30)
in withOrientation (at StackView.js:79)
in RCTView (at View.js:44)
in Transitioner (at StackView.js:22)
in StackView (created by Navigator)
in Navigator (at createKeyboardAwareNavigator.js:12)
in KeyboardAwareNavigator (created by SceneView)
in SceneView (at createTabNavigator.js:39)
in RCTView (at View.js:44)
in RCTView (at View.js:44)
in ResourceSavingScene (at createBottomTabNavigator.js:113)
in RCTView (at View.js:44)
in ScreenContainer (at createBottomTabNavigator.js:103)
in RCTView (at View.js:44)
in TabNavigationView (at createTabNavigator.js:197)
in NavigationView (created by Navigator)
in Navigator (created by SceneView)
in SceneView (created by SwitchView)
in SwitchView (created by Navigator)
in Navigator (at createAppContainer.js:388)
in NavigationContainer (at App.js:24)
in RCTView (at View.js:44)
in App (at withExpoRoot.js:22)
in RootErrorBoundary (at withExpoRoot.js:21)
in ExpoRootComponent (at renderApplication.js:34)
in RCTView (at View.js:44)
in RCTView (at View.js:44)
in AppContainer (at renderApplication.js:33)
This error is located at:
in NavigationContainer (at App.js:24)
in RCTView (at View.js:44)
in App (at withExpoRoot.js:22)
in RootErrorBoundary (at withExpoRoot.js:21)
in ExpoRootComponent (at renderApplication.js:34)
in RCTView (at View.js:44)
in RCTView (at View.js:44)
in AppContainer (at renderApplication.js:33)
The code in my .js file is:
import React from 'react';
import { ExpoConfigView } from '@expo/samples';
import { View, Text, FlatList, ActivityIndicator} from 'react-native';
import { SearchBar } from 'react-native-elements';
import restaurantList from '../assets/files/search.json';
export default class SearchScreen extends React.Component {
static navigationOptions = {
title: 'Search for Restaurants',
};
constructor() {
super();
this.state = {
loading: false,
data: {restaurantList},
error: null,
};
this.arrayholder = [];
}
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.toUpperCase()}
${item.type.toUpperCase()}`;
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
})
this.setState({
data: newData,
});
};
renderHeader = () => {
return (
<SearchBar
placeholder="Type..."
value={this.state.value}
onChange={text => this.searchFilterFunction(text)}
/>
);
};
render() {
if (this.state.loading) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<ActivityIndicator />
</View>
);
}
return (
<View>
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<Text>{item.name} {item.type}</Text>
)}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
/>
</View>
);
}
}
Here is my json file
[
{
"name": "Shake Shack",
"type":"american"
},
{
"name": "BonChon Chicken",
"type": "korean"
},
{
"name": "Starbucks",
"type:": "cafe"
}
]
Upvotes: 2
Views: 5323
Reputation: 28539
This is a really good start. However, there are a couple of issues with your code. So let's go through it and see where we can make some improvements.
Firstly in your constructor you are making the data an object rather than array. FlatLists do not work with objects they work with arrays so this is going to immediately cause you problems. You really need to remove the {}
from around the restaurantList
.
constructor() {
super();
this.state = {
loading: false,
data: {restaurantList}, // You shouldn't have {} around the restaurantList
error: null,
};
this.arrayholder = []; // we also don't need this
}
You should update your constructor to this
constructor (props) {
super(props);
this.state = {
loading: false,
data: restaurantList, // notice we have no {} around restaurantList
error: null,
value: ''
};
}
In your renderHeader
function you are using onChange
rather than onChangeText
. onChange
returns an object, but you want the text
that has been put into the search bar. You need to update your renderHeader
function to be like this.
renderHeader = () => {
return (
<SearchBar
placeholder="Type..."
value={this.state.value}
onChangeText={text => this.searchFilterFunction(text)} // now we are using the correct function to capture the text
/>
);
};
There are several issues with this function. Firstly you are looking at this.arrayholder
which is empty. We don't actually need an additional array to hold the data as we can just use the restaurantList
that we imported earlier. Secondly you are using indexOf
on a string it is better to use includes
.
searchFilterFunction = text => {
this.setState({
value: text
});
const newData = restaurantList.filter(item => {
const itemData = `${item.name.toUpperCase()} ${item.type.toUpperCase()}`;
const textData = text.toUpperCase();
return itemData.includes(textData); // this will return true if our itemData contains the textData
});
this.setState({
data: newData
});
};
In your FlatList you should use the extraData
prop as this will allow the FlatList to update when the underlying data changes. You should also add a keyExtractor
.
<FlatList
keyExtractor={(item, index) => `${index}`}
extraData={this.state} // <- add this prop
data={this.state.data}
renderItem={({ item }) => (
<Text>{item.name} {item.type}</Text>
)}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
/>
So if we put this all together, adding a mock of data so that we can check it works. We should get something like this.
// mock the data as you didn't provide an example
const restaurantList = [
{
type: 'Italian',
name: 'DiMaggio'
},
{
type: 'Greek',
name: 'Athena'
}
];
export default class SearchScreen extends React.Component {
static navigationOptions = {
title: 'Search for Restaurants'
};
constructor (props) {
super(props);
this.state = {
loading: false,
data: restaurantList,
error: null,
value: ''
};
}
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: '86%',
backgroundColor: '#CED0CE',
marginLeft: '14%'
}}
/>
);
};
searchFilterFunction = text => {
this.setState({
value: text
});
const newData = restaurantList.filter(item => {
const itemData = `${item.name.toUpperCase()} ${item.type.toUpperCase()}`;
const textData = text.toUpperCase();
return itemData.includes(textData);
});
this.setState({
data: newData
});
};
renderHeader = () => {
return (
<SearchBar
placeholder="Type..."
value={this.state.value}
onChangeText={text => this.searchFilterFunction(text)}
/>
);
};
render () {
if (this.state.loading) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<ActivityIndicator />
</View>
);
} else {
return (
<View style={styles.container}>
<FlatList
keyExtractor={(item, index) => `${index}`}
extraData={this.state}
data={this.state.data}
renderItem={({ item }) => (
<Text>{item.name} {item.type}</Text>
)}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
/>
</View>
);
}
}
}
You can see it working at the following snack https://snack.expo.io/@andypandy/flatlist-with-search
Upvotes: 2