React Native & Firebase Search - javascript

I am trying to use firebase and perform a search. The trouble I am having is when I type the search text the list data isn't being updated.
Below is the code I have come up with
componentDidMount() {
this.setState({loading: true});
var merchants = new Array;
merchantsRef.once('value').then((snapshot) => {
snapshot.forEach((child) => {
merchants.push({
id: child.val().id,
type: child.val().type,
attributes: {
coming_soon: child.val().attributes.coming_soon,
featured_image_url: child.val().attributes.featured_image_url,
has_locations: child.val().attributes.has_locations,
highlight: child.val().attributes.highlight,
logo_url: child.val().attributes.logo_url,
main_image_url: child.val().attributes.main_image_url,
name: child.val().attributes.name,
offer_active: child.val().attributes.offer_active,
offer_cta_title: child.val().attributes.offer_cta_title,
offer_ends_at: child.val().attributes.offer_ends_at,
offer_starts_at: child.val().attributes.offer_starts_at,
offer_teaser: child.val().attributes.offer_teaser,
offer_terms: child.val().attributes.offer_terms,
offer_text: child.val().attributes.offer_text,
offer_url: child.val().attributes.offer_url,
permalink: child.val().attributes.permalink,
shop_url: child.val().attributes.shop_url,
},
_key: child.key
})
})
this.setState({
data: merchants,
loading: false,
})
// console.log(this.state.data)
}).catch((error) => {
console.log('Error fetching retailer data:', error)
})
}
searchFilterFunction = text => {
merchantsRef.orderByChild('name').startAt(text.toString()).on('value', function(child) {
console.log(child.val());
});
};
_renderEmpty = () => {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<ActivityIndicator />
</View>
)
}
_renderItem = ({item}) => (
<MerchantRow data={item} />
)
_keyExtractor = (item, index) => item._key;
render() {
if (this.state.loading) {
console.log("Loading")
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<ActivityIndicator
animating
size="large"
/>
<Text>Fetching shops...</Text>
</View>
)
}
return (
<View style={styles.container}>
<SearchBar
placeholder='Search for retailers...'
containerStyle={{backgroundColor: Colors.blue, borderTopWidth: 0, borderBottomWidth: 0}}
inputStyle={{backgroundColor: Colors.snow}}
onChangeText={text => this.searchFilterFunction(text)}
autoCorrect={false}
/>
<ScrollView style={styles.container}>
<FlatList
data={this.state.data}
renderItem={this._renderItem.bind(this)}
keyExtractor={this._keyExtractor}
initialNumToRender={9}
ItemSeparatorComponent={this._renderSeparator}
removeClippedSubviews={false}
ListEmptyComponent={this._renderEmpty}
/>
</ScrollView>
</View>
)
}

#Paul'Whippet'McGuane, good to know. That's likely because the context of this isn't defined. In order to maintain the context of the keyword this, you can modify the searchFilterFunction like so:
searchFilterFunction = text => {
const that = this;
merchantsRef.orderByChild('name').startAt(text.toString()).on('value', function(child) {
console.log(child.val());
const val = child.val();
that.setState({ data: val });
});
};

Related

How to make reusable component in react-native and call them inside other component?

I have a modal component that I want to reuse in future component.
For this reason I want to have it in its own file.
I don't manage to call the component inside the other properly though.
Here is my last attempt of it.
The Modal component code:
export const FlyingDescription = (cityDescription) => {
const [modalVisible, setModalVisible] = useState(false);
return (
<View>
<Modal
animation="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => setModalVisible(!modalVisible)}
>
<View style={styleBoxParent}>
<View style={styleBoxChildren}>
<LinearGradient
style={styleLinearGradient}
colors={['#136a8a', '#267871']}
start={[0, 0.65]}
>
<Text style={styleText}>{cityDescription}</Text>
<Pressable
onPress={() => setModalVisible(!modalVisible)}
>
<Text style={styleText}>Close</Text>
</Pressable>
</LinearGradient>
</View>
</View>
</Modal>
</View>
);
};
The component where I want to call the Modal component:
import FlyingDescription from './FlyingDescription.js';
var utils = require('./utils');
export default class SearchScreen extends React.Component {
constructor(props) {
super(props);
this.navigation = props.navigation;
this.state = {
searchInput: '',
item : {},
renderSearch: false,
renderFlyingDescription: false,
};
this.errorMessage = 'Search for cities...';
}
resetState = () => {
this.setState({
item: {},
renderable: false,
}); }
setSearchInput = (value) => {
this.setState({
searchInput: value
});
}
searchCity = () => {
this.resetState();
utils.fetchWeather(this.state.searchInput).then(response => {
if (response.cod == 200) {
console.log(response);
this.setItemState(
{
name: response.name,
temp: Math.ceil(response.main.temp),
type: response.weather[0].main,
desc: 'Humidity: ' + response.main.humidity + '% - ' + response.weather[0].main
}
);
this.setRenderSearch();
}
});
}
setItemState = (newItem) => {
this.setState(
{
item: newItem,
}
);
}
setRenderSearch = () => {
this.setState(
{
renderSearch: true,
}
);
}
render = () => {
return(
<View style={utils.style.container}>
<StatusBar barStyle="light-content" />
<Text style={utils.style.titleContainer}>☀️ CityWeather</Text>
<View style={{alignItems: 'center', width:'90%'}}>
<Text>Search for a city</Text>
<TextInput
onChangeText={(value) => this.setSearchInput(value)}
value={this.searchInput}
style={{ width: '80%', padding: 15, margin: 5, backgroundColor: 'black', color: 'white' }}
/>
<TouchableHighlight
style={{backgroundColor: 'grey', padding: 20, borderRadius: 8}}
onPress={this.searchCity}
>
<Text style={{fontSize: 14, color:'white'}}>Search</Text>
</TouchableHighlight>
</View>
{ this.state.renderSearch ? (
<TouchableHighlight
underlayColor="white"
onPress={ () => this.setState({renderFlyingDescription: true})}
>
<LinearGradient
colors={['rgba(0,0,0,0.05)', 'rgba(0,0,0,0)']}
start={[0, 0.5]}
>
<View style={utils.style.row}>
<Text style={[utils.getTempRange(this.state.item.temp), utils.style.temp]}>
{utils.getEmoji(this.state.item.type)} {this.state.item.temp} °C
</Text>
<Text style={utils.style.cityName}>{this.state.item.name}</Text>
</View>
</LinearGradient>
</TouchableHighlight>
) : (
// WHERE I WANT TO RENDER MY MODAL COMPONENT
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center', fontSize: 32}}>
<Text>{this.errorMessage}</Text>
</View>
)
}
{
renderFlyingDescription(this.state.renderFlyingDescription, this.state.item.desc)
}
</View>
);
}
}
export function renderFlyingDescription(isInterpretable, cityDescription) {
if(isInterpretable) {
return <FlyingDescription cityDescription={cityDescription} />
}
}

react-native how do I show activityindicator while file uploading

I want to have a loading spinner appear in the middle of the screen while the file is being uploaded.
The clicked file is loaded and appears in the file list as well.
I imported ActivityIndicator from 'react-native'.
When I wrote the code after as a test, the loading spinner was well seen.
However, I wanted to make the spinner appear only the file is loaded. So, I wrote the code, but the loading spinner does not appear.
import {Text,View,ImageBackground,Image,StyleSheet,TouchableOpacity,FlatList,Button,ActivityIndicator} from 'react-native';
export default function FileUpload({route, navigation}) {
const [multipleFile, setMF] = useState([])
const [isLoading, setLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 5000);
}, [])
const addEntry = (data) => {
let newObj = {
"id": randId(),
"name" : data.filename,
"text" : data.text,
}
console.log('Finish load');
setMF([...multipleFile, newObj]);
console.log(multipleFile)
};
const randId = () => {
let id = ''
for (var i = 0; i < 10; i++) {
id += String.fromCharCode(Math.trunc(Math.random()*85) + 48)
}
return id
}
const sendFile = () => {
axios({
method: 'post',
url: '',
data: {
"uri": ""
}
}).then((res) =>{
addEntry(res.data)
}
).catch((err) => console.log(err));
}
const renderItem = ({ item }) => (
<Text>{item.name}</Text>
);
return (
<ImageBackground source={require('../images/bg2.png')}
style={{width: '100%', height: '100%'}}>
<View>
<TouchableOpacity onPress={() => {
DocumentPicker.getDocumentAsync().then((res) => {
console.log(res)
if (res.type == 'success') {
console.log('load start');
if(isLoading){
return(
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<ActivityIndicator size="large"/>
</View>
);
}
sendFile()
} else {
console.log('you cancelled')
}
})
}}
style={{alignItems: 'center'}}>
<Image source ={require('../images/cloud.png')}
style={styles.image}
/>
<View style={styles.viewTextStyle}>
<Text style={styles.textStyle}>{'Upload Files'} </Text>
<Text style={{fontSize:60, color:'white'}}> + </Text>
<Text style={styles.textStyle1}>{'Browse and select your file \n you want to upload'}</Text>
</View>
</TouchableOpacity>
</View>
<View style={styles.listHeader}>
<Text style={{textAlign: 'center', fontSize: 18}}> File List </Text>
</View>
<FlatList
style={styles.scrollContainer}
data={multipleFile}
renderItem={renderItem}
keyExtractor={item => item.id.toString()}>
</FlatList>
{/* <View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<ActivityIndicator size="large"/>
</View> */}
<TouchableOpacity onPress={() => {
// console.log(multipleFile[0].name)
navigation.navigate('Details Search',
multipleFile[0]
)}}>
<View style={styles.button}>
<Text style={styles.buttonText}>Next</Text>
</View>
</TouchableOpacity>
</ImageBackground>
);
}
set the default state to false
const [isLoading, setLoading] = useState(false);
remove this effect
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 5000);
}, [])
remove loading view from DocumentPicker.getDocumentAsync()
if (res.type == 'success') {
sendFile()
} else {
console.log('you cancelled')
}
set loading true/false in sendFile()
const sendFile = () => {
setLoading(true);
axios({
method: 'post',
url: '',
data: {
"uri": ""
}
}).then((res) =>{
setLoading(false)
addEntry(res.data)
}
).catch((err) => setLoading(false));
}
const renderItem = ({ item }) => (
<Text>{item.name}</Text>
);
add loading view in main return
return (
....
if(isLoading){
return(
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<ActivityIndicator size="large"/>
</View>
);
}
....
)

too many rerenders on using useState

I am calling the contactsfunction from the main return. I don't see any error until here:
const showContacts = React.useCallback(
(data: UsersQueryHookResult) => {
if (data) {
return (
<View style={styles.users}>
</View>
);
}
},
[userData],
);
const contacts = () => {
console.log('running');
const { loading, error, data } = useUsersQuery({
variables: {
where: { id: 34 },
},
});
console.log('DATA COMING', data);
//setUserData(data);
//console.log('contact name', data.users.nodes[0].userRelations[0].relatedUser.firstName);
};
return (
<SafeAreaView style={{ flex: 1 }}>
<Container style={{ flex: 1, alignItems: 'center' }}>
<Item style={styles.addToWhitelist}>
<Icon
name="add"
onPress={() => navigation.navigate('AddContactTry')}
/>
<Text style={styles.addToContactTitle}>Add contact</Text>
</Item>
<Text onPress={() => navigation.navigate('Home')}>Zurück</Text>
<View style={{ width: moderateScale(350) }}>
<Text>Keine Kontacte</Text>
</View>
{contacts()}
</Container>
</SafeAreaView>
);
};
Now I want to do some conditional rendering on the basis of the results from contacts. However, as soon as I uncomment setUserData(data); I get an error that too many re-renders. I don't get what I am doing wrong in showUsersto get this error.
Edit:
I tried this but it gives an invalid hook call error:
export const Whitelist: React.FunctionComponent = (props) => {
const [userData, setUserData] = useState<UsersQueryHookResult>('');
useEffect(() => {
// Your function to fetch/get contacts
const data = contacts();
setUserData(data);
}, [])
const showContacts = React.useCallback(
(data: UsersQueryHookResult) => {
if (data) {
return (
<View style={styles.users}>
</View>
);
}
},
[userData],
);
const contacts = () => {
console.log('running');
const { loading, error, data } = useUsersQuery({
variables: {
where: { id: 34 },
},
});
console.log('DATA COMING', data);
//setUserData(data);
};
return (
<SafeAreaView style={{ flex: 1 }}>
<Container style={{ flex: 1, alignItems: 'center' }}>
<Item style={styles.addToWhitelist}>
<Icon
name="add"
onPress={() => navigation.navigate('AddContactTry')}
/>
<Text style={styles.addToContactTitle}>Add contact</Text>
</Item>
<Text onPress={() => navigation.navigate('Home')}>Zurück</Text>
{/* {contacts()} */}
</Container>
</SafeAreaView>
);
};
Try to fetch contacts in componentDidMount method or useEffect (in your case with hooks) and store the result in a state with useState.
const [contacts, setContacts] = React.useState(null);
[...]
React.useEffect(() => {
// Your function to fetch/get contacts
const data = contacts();
setContacts(data);
}, [])
[...]
return (
<SafeAreaView style={{ flex: 1 }}>
[...]
{contacts && contacts.map(contact => (<View>{contact.name}</View>)}
[...]
</SafeAreaView>
);
hope it helps.

how do i get and show the total sum objects that arrive from my JSON in a footer at the bottom of the screen?

At my example, the function “getData” loading my data, but after the loading, I try to print and show the total sum of the objects that came from JSON in a footer at the bottom of the screen.
and I don't really know how to do it.
I don't understand how to solve this issue coz I have tried many ways.
This is my example:
export default class MainScreen extends Component {
constructor(props) {
super(props);
this.state = { data: [] };
}
getData = () => {
this.setState({ isLoading: true })
axios.get("https://rallycoding.herokuapp.com/api/music_albums")
.then(res => {
this.setState({
isLoading: false,
data: res.data
});
console.log(res.data);
});
}
componentDidMount() {
this.props.navigation.setParams({getData: this.getData}); //Here I set the function to parameter
this.getData()
}
renderItem(item) {
const { title, artist} = item.item;
return (
<TouchableOpacity
onPress={() => this.props.navigation.navigate("Settings")}
>
<Card
containerStyle={{
borderColor: "black",
padding: 20,
height: 100,
backgroundColor: "#e6e6ff",
borderBottomEndRadius: 10,
borderTopRightRadius: 10,
borderBottomStartRadius: 10,
}}
>
<View
style={{
paddingVertical: 15,
paddingHorizontal: 10,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center"
}}
>
<Icon name="chevron-right" size={30} color={"grey"} justifyContent={"space-between"} />
<Text style={styles.name}>
{title+ " " + artist}
</Text>
{/* <Text style={styles.vertical} numberOfLines={2}></Text> */}
</View>
</Card>
</TouchableOpacity>
);
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, paddingTop: 230 }}>
<Text
style={{ alignSelf: "center", fontWeight: "bold", fontSize: 20 }}
>
loading data...
</Text>
<ActivityIndicator size={'large'} color={'#08cbfc'} />
</View>
);
}
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
renderItem={this.renderItem.bind(this)}
keyExtractor={item => item.id}
/>
</View>
);
}
}
/////////////////////////////////////////////////////////
MainScreen.navigationOptions = navData => {
return {
headerTitle: 'melon',
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title=**"sync button"**
iconName={Platform.OS === "android" ? "md-sync" : "ios-sync"}
onPress={() => {
navData.navigation.navigate("getData");
}}
/>
</HeaderButtons>
)
};
};
If type of data is array you can get total number of elements by this.state.data.length
If type of data is object you can get total number of elements by Object.keys(data).length

React-native search bar - error: undefined is not a function (near '... this.state.books.filter...')

I can't figure out what I am doing wrong here.
I added a searchbar but when I write something in the textfield it gives me the error: "undefined is not a function (near '... this.state.books.filter...')"
Below is some of my code.
What am I doing wrong?
state = {
books: {},
};
componentDidMount() {
firebase
.database()
.ref('/Books')
.on('value', snapshot => {
this.setState({ books: snapshot.val() });
});
}
searchBooks = value => {
const { books } = this.state;
const bookArray = Object.values(books);
const filteredBooks = bookArray.filter(book => {
let bookLowercase = (book.bookname).toLowerCase();
let searchTermLowercase = value.toLowerCase();
return bookLowercase.indexOf(searchTermLowercase) > -1;
});
this.setState({ books: filteredBooks });
};
render() {
const { books } = this.state;
if (!books) {
return null;
}
const bookArray = Object.values(books);
const bookKeys = Object.keys(books);
return (
<View style={styles.container}>
{/*Searchbar */}
<View style={{ height: 80, backgroundColor: '#c45653', justifyContent: "center", paddingHorizontal: 5 }}>
<View style={{ height: 50, backgroundColor: 'white', flexDirection: 'row', padding: 5, alignItems: 'center' }}>
<TextInput
style={{
fontSize: 24,
marginLeft: 15
}}
placeholder="Search"
onChangeText={value => this.searchBooks(value)}
/>
</View>
</View>
<FlatList
style={{ backgroundColor: this.state.searchBarFocused ? 'rgba(0,0,0,0.3)' : 'white' }}
data={bookArray}
keyExtractor={(item, index) => bookKeys[index]}
renderItem={({ item, index }) => (
<BookListItem
book={item}
id={bookKeys[index]}
onSelect={this.handleSelectBook}
/>
)}
/>
</View>
);
You're initializing this.state.books as an Object.
state = {
books: {},
};
Objects have no filter function. You probably want to use an Array instead.
state = {
books: [],
};
Then you'll be able to use this.state.books.filter() with the Array's filter function.

Categories