I have a users collection which I want to query as the user types, but when I switch to the Search Users Screen, all the users have populated on the screen even the search is empty.
How can I prevent this and only send the results to the client when he/she types in the search box??
I found some other links and resources but either they were too old, difficult to understand for me or using React instead of React Native.
I am a beginner developer and if you would be elaborate with the solution then that would be great.
This is my code:
SEARCH_USER.JS:
import React, {Component} from 'react';
import {
View,
Text,
TextInput,
Alert,
FlatList,
ActivityIndicator,
} from 'react-native';
import firestore from '#react-native-firebase/firestore';
import algoliasearch from 'algoliasearch';
import {
InstantSearch,
connectSearchBox,
connectInfiniteHits,
} from 'react-instantsearch-native';
import PropTypes from 'prop-types';
import InfiniteHits from '../components/Algolia Components/InfiniteHits';
class SearchAddBuddyScreen extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
value: null,
};
}
searchClient = algoliasearch(
'########',
'####################',
);
render() {
return (
<View>
<SecondaryHeader secondaryHeaderTitle="Add Buddies" />
<InstantSearch
searchClient={this.searchClient}
indexName="prod_USERSCOLLECTION"
onSearchStateChange={searchState =>
console.log('=====> ', searchState)
}>
<ConnectedSearchBox />
<InfiniteHits navigation={this.props.navigation} />
</InstantSearch>
</View>
);
}
}
class SearchBox extends Component {
render() {
console.log('Connected Search Box called');
return (
<View
style={[
textInput.generalTextInput,
{
marginBottom: 24,
alignSelf: 'center',
justifyContent: 'center',
},
]}>
<TextInput
placeholderTextColor="#333647"
style={[textInput.generalTextInput, {alignSelf: 'center'}]}
onChangeText={text => this.props.refine(text)}
value={this.props.currentRefinement}
placeholder={'Search'}
clearButtonMode={'always'}
spellCheck={false}
autoCorrect={false}
autoCapitalize={'none'}
/>
</View>
);
}
}
SearchBox.propTypes = {
refine: PropTypes.func.isRequired,
currentRefinement: PropTypes.string,
};
const ConnectedSearchBox = connectSearchBox(SearchBox);
export default SearchUserScreen;
InfiniteHits.js:
import React, {Component} from 'react';
import {StyleSheet, Text, View, FlatList} from 'react-native';
import {connectInfiniteHits} from 'react-instantsearch-native';
import AddFriendComponent from '../AddFriend';
class InfiniteHits extends Component {
constructor(props) {
super(props);
this.navigation = this.props.navigation;
}
_renderItem = ({item}) => {
return (
<AddFriendComponent
username={item.username}
fullName={item.fullName}
onPressUserProfile={() =>
this.props.navigation.navigate('UserProfile', {profileId: item.uid})
}
/>
);
};
render() {
return (
<FlatList
data={this.props.hits}
keyExtractor={item => item.objectID}
// ItemSeparatorComponent={() => <View style={styles.separator} />}
onEndReached={() => this.props.hasMore && this.props.refine()}
renderItem={this._renderItem}
/>
);
}
}
export default connectInfiniteHits(InfiniteHits);
After reading through more material I found this https://www.algolia.com/doc/guides/building-search-ui/going-further/conditional-requests/react/
And made the following changes to my code and it succeeded.
conditionalQuery = {
search(requests) {
if (
requests.every(({params}) => !params.query) ||
requests.every(({params}) => params.query === ' ') ||
requests.every(({params}) => params.query === ' ') ||
requests.every(({params}) => params.query === ' ')
) {
// Here we have to do something else
console.log('Empty Query');
return Promise.resolve({
results: requests.map(() => ({
hits: [],
nbHits: 0,
nbPages: 0,
processingTimeMS: 0,
})),
});
}
const searchClient = algoliasearch(
'########',
'###################',
);
return searchClient.search(requests);
},
};
render() {
return (
<View>
<SecondaryHeader secondaryHeaderTitle="Add Buddies" />
<InstantSearch
searchClient={this.conditionalQuery}
indexName="prod_USERSCOLLECTION"
onSearchStateChange={searchState =>
console.log('=====> ', searchState)
}>
<ConnectedSearchBox />
<InfiniteHits navigation={this.props.navigation} />
</InstantSearch>
</View>
);
}
This solution can still be improved for the spaces typed, but I don't know how to do that.
Related
I am new to react native. I have following component in my project for now I have written for fetching API in same component but want to separate it out. I am getting difficulty for how can i access variable which I am using in "getAlbum" method from outside of component.
I am trying to do this using new concept context API . is this possible using context API and how?
Is there standard way to separate API call from component?
import React, { Component } from 'react';
import {
FlatList, Text, View, Image, TouchableOpacity,
} from 'react-native';
import { ActivityIndicator, Provider } from 'react-native-paper';
import axios from 'axios';
import styles from '../style/ThumbnailView.component.style';
import ErrorAlert from '../common/ErrorAlert';
import * as myConstant from '../common/Constants';
export default class HomeScreen extends Component {
// For to Navigation header
static navigationOptions = () => ({
headerTitle: 'Album Information',
});
constructor(props) {
super(props);
this.state = {
isLoading: true,
apiLoadingError: false,
};
}
getAlbums() {
const { navigation } = this.props;
const albumId = navigation.getParam('albumID', 'no data');
axios
.get(
myConstant.API + `photos?albumId=${albumId}`, {timeout: myConstant.TIMEOUT}
)
.then((response) => {
this.setState({
isLoading: false,
dataSource: response.data,
});
})
.catch(err => {
this.setState({isLoading: false, apiLoadingError: true})
});
}
componentDidMount() {
this.getAlbums();
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, paddingTop: 30 }}>
<ActivityIndicator animating={true} size='large' />
</View>
);
}
if (this.state.apiLoadingError) {
return (
<ErrorAlert />
);
}
return (
<React.Fragment>
<Provider>
<View style={styles.listContainer} >
<FlatList
testID='flatlist'
data={ this.state.dataSource } numColumns={3}
renderItem={({ item }) => <View style={styles.listRowContainer}>
<TouchableOpacity onPress={() => this.props.navigation.navigate('AlbumDetailsViewScreen', {
albumTitle: item.title, albumImg: item.url
})} style={styles.listRow}>
<View style={styles.listTextNavVIew}>
<Image source = {{ uri: item.thumbnailUrl }} style={styles.imageViewContainer} />
</View>
</TouchableOpacity>
</View>
}
keyExtractor = { (item, index) => index.toString() }
/>
</View>
</Provider>
</React.Fragment>
);
}
}
I am new to react native. I have following component in my project for now I have written for fetching API in same component but want to separate it out. I am getting difficulty for how can i access variable which I am using in "getAlbum" method from outside of component.
Is there standard way to separate API call from component?
import React, { Component } from 'react';
import {
FlatList, Text, View, Image, TouchableOpacity,
} from 'react-native';
import { ActivityIndicator, Provider } from 'react-native-paper';
import axios from 'axios';
import styles from '../style/ThumbnailView.component.style';
import ErrorAlert from '../common/ErrorAlert';
import * as myConstant from '../common/Constants';
export default class HomeScreen extends Component {
// For to Navigation header
static navigationOptions = () => ({
headerTitle: 'Album Information',
});
constructor(props) {
super(props);
this.state = {
isLoading: true,
apiLoadingError: false,
};
}
getAlbums() {
const { navigation } = this.props;
const albumId = navigation.getParam('albumID', 'no data');
axios
.get(
myConstant.API + `photos?albumId=${albumId}`, {timeout: myConstant.TIMEOUT}
)
.then((response) => {
this.setState({
isLoading: false,
dataSource: response.data,
});
})
.catch(err => {
this.setState({isLoading: false, apiLoadingError: true})
});
}
componentDidMount() {
this.getAlbums();
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, paddingTop: 30 }}>
<ActivityIndicator animating={true} size='large' />
</View>
);
}
if (this.state.apiLoadingError) {
return (
<ErrorAlert />
);
}
return (
<React.Fragment>
<Provider>
<View style={styles.listContainer} >
<FlatList
testID='flatlist'
data={ this.state.dataSource } numColumns={3}
renderItem={({ item }) => <View style={styles.listRowContainer}>
<TouchableOpacity onPress={() => this.props.navigation.navigate('AlbumDetailsViewScreen', {
albumTitle: item.title, albumImg: item.url
})} style={styles.listRow}>
<View style={styles.listTextNavVIew}>
<Image source = {{ uri: item.thumbnailUrl }} style={styles.imageViewContainer} />
</View>
</TouchableOpacity>
</View>
}
keyExtractor = { (item, index) => index.toString() }
/>
</View>
</Provider>
</React.Fragment>
);
}
}
You can separate your axios call by making another class with function which will receive 'albumID' as an argument - then add it to your axios link. If you want to call this function from another class just make it static and use like in example below. Then you can map your fetchData to parse it into state. Hope it will help you.
export class Api {
static fetchData = (albumId: string) => {
//here your axios call which will return an array
}
}
export default class HomeScreen extends React.Component {
state = {
//.....
}
receivedData = Api.fetchData('albumID');
//you can map array here to get what you want.
}
I am trying to create a list of custom inputs based on an array, and when pressing the the enter key, I'd like the focus to automatically move to the next custom input. I can get this to work with a regular <TextInput> react component using the ref and onSubmitEditing but I cannot get this to function properly using my custom component that wraps a <TextInput>
Here is my code, it consists of two files: App.js and TextInput2.js (I know that currently the last line will error because of the reference counter but if I can get it to work I'll address the last issue)
Working Snack
-- App.js --
import React from 'react';
import { StyleSheet, View, TextInput } from 'react-native';
import TextInput2 from './TextInput2'
export default class App extends React.Component {
constructor(){
super();
this.myRef = [];
this.state = {}
}
focusField = (key) => {
this.myRef[key].focus()
}
render() {
let textFields = ["one", "two", "three", "four", "five"];
return (
<View style={styles.container}>
{
textFields.map((x, i) => {
this.myRef[i] = React.createRef();
let k = i + 1
return(
<TextInput2
name={x}
key={i}
placeholder={x + " This Doesnt Work"}
ref={ref => this.myRef[i] = ref}
nextRef={this.myRef[k]}
//onSubmitEditing={() => this.focusField(k)}
//onSubmitEditing={() => this.myRef[k].focus()}
blurOnSubmit={false}
/>
)
})
}
{
textFields.map((x, i) => {
this.myRef[i] = React.createRef();
return(
<TextInput
name={x}
key={i}
placeholder="This works!"
ref={ref => this.myRef[i] = ref}
onSubmitEditing={() => this.focusField(i+1)}
blurOnSubmit={false}
/>
)
})
}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
});
--TextInput2.js --
import React from 'react';
import { View, TextInput } from 'react-native';
export default class TextInput2 extends React.Component {
state={}
handleFocus = () => {}
handleBlur = () => {}
focus() {
this.props.nextRef.focus()
}
render() {
return (
<View >
<TextInput
{...this.props}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
onSubmitEditing={() => this.focus()}
/>
</View>
)
}
}
I've read this post and this but cannot seem to determine how to setup the function to set focus on the next field.
I have edited the Snack. Please try this
I think you're making it complicated. Try to change like this,
this.myRef[index] = React.createRef()
CustomTextComponent component
<CustomTextComponent
name={Something}
key={index}
forwardRef={this.myRef[index]}
onSubmitEditing={() => this.myRef[index + 1].current.focus()}
/>
As you're using createRef() you have to call it's ref using the "current" object.
CustomComponent.js
import React from 'react';
import { View, TextInput } from 'react-native';
export default class CustomComponent extends React.Component {
render() {
return (
<View >
<TextInput
{...this.props}
returnKeyType={"next"}
ref={this.props.forwardRef}
onSubmitEditing={this.props.onSubmitEditing}
/>
</View>
)
}
}
I'm rendering the data got from an API into the Cards, I created a CardContainers component that map the data I get from the API then use that component in another component.
CardContainers.js
import React from 'react';
import {View} from 'react-native';
import {withNavigation} from 'react-navigation';
class CardContainers extends React.Component{
addPlace(){
return this.props.addPlace;
}
renderCards(){
return this.props.data.map((item, index) => {
return (
<View key={index}>
{this.props.renderCard(item)}
</View>
)
})
}
render(){
return(
<View>{this.renderCards()}</View>
)
}
}
PlacesList.js
import React from 'react';
import ProgressiveInput from 'react-native-progressive-input';
import {StyleSheet, View, Alert, Text, TouchableOpacity, ListView, ScrollView} from 'react-native';
import {Button, Card, Icon} from 'react-native-elements';
import {connect} from 'react-redux';
import CardContainers from './CardContainers';
import * as placesActions from '../redux/actions/placesActions';
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
});
class PlacesList extends React.Component{
static navigationOptions = ({navigation}) => {
return {
title:'Finding new places',
}
}
constructor(props){
super(props)
const position = this.props.navigation.getParam('position')
const tripId = JSON.stringify(this.props.navigation.getParam('tripId')).replace(/\"/g,"");
const date = JSON.stringify(this.props.navigation.getParam('date')).replace(/\"/g,"");
this.state={
position: position,
tripId: tripId,
date: date,
dataSource:ds.cloneWithRows([]),
}
}
renderCard(item){
return(
<Card
key={item.id}
title={item.title}
titleStyle={styles.title}
containerStyle={{marginTop: 20, marginBottom:20}}
>
<View style={{flexDirection: 'row'}}>
<Text style={{margin:10}}>
Category: {item.category.title}
</Text>
<Text style={{margin:10}}>
Rating: {item.averageRating}
</Text>
</View>
<View style ={{flexDirection:'row', alignItems:'center', alignSelf:'center'}}>
<Button
icon={<Icon name='add' color='#ffffff'/>}
buttonStyle={{marginLeft:15, borderRadius:10}}
title='ADD THIS PLACE'
type='solid'
onPress={()=>this.props.addPlace()}
/>
</View>
</Card>
);
}
getPlacesAroundDestination = () => {
this.props.aroundPlaces(this.state.position);
this.setState({dataSource: ds.cloneWithRows(this.props.placesAround)})
}
autoComplete = async(query) => {
this.setState({destination: query})
await this.props.suggestPlaces(this.state.position, query)
this.setState({dataSource: ds.cloneWithRows(this.props.searchSuggests)})
}
inputCleared = () => {
this.setState({
destination:'',
isLoading: false,
dataSource: ds.cloneWithRows({}),
});
}
onListItemClicked = (searchSuggests) => {
this.setState({
title: searchSuggests.title,
placeId: searchSuggests.id,
openingHours: searchSuggests.openingHours,
category: searchSuggests.category,
position:searchSuggests.position.toString(),
dataSource:ds.cloneWithRows([]),
})
}
renderRow = (searchSuggests) => {
return(
<TouchableOpacity
style={{padding:10}}
onPress={()=>this.onListItemClicked(searchSuggests)}
>
<Text style={{fontSize:20}}>{searchSuggests.title}</Text>
<Text style={{fontSize:10}}>{searchSuggests.vicinity}</Text>
</TouchableOpacity>
)
}
renderSeparator = () => {
return <View style={{borderWidth:0.5, borderColor:'lightgrey',}}> </View>
}
renderContent(){
return (
<CardContainers
data={this.props.placesAround.items}
renderCard={this.renderCard}
/>
)
}
render(){
return(
<View style={styles.container}>
<ProgressiveInput
style={{marginTop:20, marginLeft:10, marginRight:10}}
placeholder='Your destination...'
value={this.state.destination}
isLoading={this.props.isLoading}
onChangeText={this.autoComplete}
onInputCleared={this.inputCleared}
/>
<View style={{flex:0}}>
<ListView
enableEmptySections
style={{backgroundColor:'white', margin:20}}
dataSource={this.state.dataSource}
renderRow={this.renderRow}
renderSeparator={this.renderSeparator}
/>
</View>
<Button
title= 'SUGGEST'
style={{alignSelf:'center'}}
onPress={() => this.props.aroundPlaces(this.state.position)}
/>
<ScrollView>
{this.props.placesAround.items? this.renderContent():null}
</ScrollView>
</View>
)
}
}
const mapStateToProps = (state) => {
return{
searchSuggests: state.places.searchSuggests,
isLoading: state.places.isLoading,
placesAround: state.places.placesAround,
geolo: state.location.latitude + ',' + state.location.longitude,
}
}
const mapDispatchToProps = (dispatch) => {
return {
addPlace:(tripId, date, title, category, rating, placeID) => dispatch (placesOfPlanActions.addPlace(tripId, date, title, category, rating, placeID)),
aroundPlaces: (geolo) => dispatch(placesActions.aroundPlaces(geolo)),
suggestPlaces: (geolo, destination) => dispatch(placesActions.suggestPlaces(geolo, destination))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PlacesList);
As you can see in the code below, I want to call the addPlace() function which is a redux action in the onPress event of each Card rendered, but I cannot do it because the CardContainers does not have that function inside. So is there any way that I can do it? I'm quite new to react-native and redux, just spent 4 months on this and I do not think that I fully understand it.
Yup, simply add the connect HOC to CardContainers and you should be able to set the function in mapDispatchToProps like you did in PlacesList.
I'm trying to get the images inside my getImageForLogo.js to load through a loop.
That file looks like so:
const images = {
logo1: require('../assets/logo1.jpeg'),
logo2: require('../assets/logo2.png'),
'logo with space': require('../assets/logo-with-space.jpeg'),
};
export default logos => images[logos];
My App.js:
import React from 'react';
import {
StyleSheet,
View,
Image,
ScrollView,
Text
} from 'react-native';
import getImageForLogo from './utils/getImageForLogo';
import Avatar from './components/Avatar';
export default class App extends React.Component {
constructor (props) {
super(props);
this.state = {
logos: 'logo1'
};
}
render () {
const {
logos
} = this.state;
const form = { 'Logo1': 'assets/logo.jpg', 'Logo2': 'assets/logo2.jpg', 'Logo3': 'assets/logo3.jpg' };
return (
<View style={styles.appContainer}>
<View style={styles.titleContainer}>
<Text style={styles.title}>Title</Text>
</View>
<ScrollView style={styles.timerlist}>
Object.values(form).map((key, val) => (
<Avatar
initials="KC"
size={75}
key={val}
source={key}
backgroundColor={'blue'}
onPressLinkImage={() => {
console.log('Pressed!');
}}
/>
));
</ScrollView>
</View>
);
}
}
I created the const form just to try and see if the loop works. I'm trying to cycle by using:
Object.values(form).map((key, val) => (
<Avatar
initials="KC"
size={75}
key={val}
source={key}
backgroundColor={'blue'}
onPressLinkImage={() => {
console.log('Pressed!');
}}
/>
));
however, when I run this, the app crashes and says ReferenceError: Can't find variable: val. I can't figure out why this happens because if I run this code on the Chrome terminal, it works.
EDIT:
Here's Avatar.js:
import {
ColorPropType,
StyleSheet,
Text,
View,
Image,
TouchableOpacity
} from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';
import getImageForLogo from '../utils/getImageForLogo';
export default function Avatar({
size,
backgroundColor,
initials,
source,
onPressLinkImage,
}) {
const style = {
width: size,
height: size,
borderRadius: size / 2,
backgroundColor,
};
const image = getImageForLogo(source)
return (
<View style={[styles.container, style]}>
<TouchableOpacity onPress={onPressLinkImage}>
<Image source={image} style={styles.image} />
</TouchableOpacity>
</View>
);
}
Avatar.propTypes = {
initials: PropTypes.string.isRequired,
size: PropTypes.number.isRequired,
source: PropTypes.string.isRequired,
backgroundColor: ColorPropType.isRequired,
onPressLinkImage: PropTypes.func.isRequired,
};
Your code in the App.js appears to be missing some { } around your Object.values.
So instead of putting inside your ScrollView
Object.values(form).map((key, val) => (
<Avatar
initials="KC"
size={75}
key={val}
source={key}
backgroundColor={'blue'}
onPressLinkImage={() => {
console.log('Pressed!');
}}
/>
)
);
you should put
{Object.values(form).map((key, val) => (
<Avatar
initials="KC"
size={75}
key={val}
source={key}
backgroundColor={'blue'}
onPressLinkImage={() => {
console.log('Pressed!');
}}
/>
)
)}
notice that it is contained inside the { }.
Here is it inside your App.js
import React from 'react';
import {
StyleSheet,
View,
Image,
ScrollView,
Text
} from 'react-native';
import getImageForLogo from './utils/getImageForLogo';
import Avatar from './components/Avatar';
export default class App extends React.Component {
constructor (props) {
super(props);
this.state = {
logos: 'logo1'
};
}
render () {
const {
logos
} = this.state;
const form = { 'Logo1': 'assets/logo.jpg', 'Logo2': 'assets/logo2.jpg', 'Logo3': 'assets/logo3.jpg' };
return (
<View style={styles.appContainer}>
<View style={styles.titleContainer}>
<Text style={styles.title}>Title</Text>
</View>
<ScrollView style={styles.timerlist}>
{Object.values(form).map((key, val) => (
<Avatar
initials="KC"
size={75}
key={val}
source={key}
backgroundColor={'blue'}
onPressLinkImage={() => {
console.log('Pressed!');
}}
/>
))}
</ScrollView>
</View>
);
}
}
Also note your Object.values(form).map((key, val) will give the following keys and values
key | val
assets/logo.jpg | 0
assets/logo1.jpg | 1
assets/logo2.jpg | 2
For readability of your code I would change your code to the following:
{Object.values(form).map((source, key) => (
<Avatar
initials="KC"
size={75}
key={key}
source={source}
backgroundColor={'blue'}
onPressLinkImage={() => {
console.log('Pressed!');
}}
/>
)
)}