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.
Related
Let's say I create a login screen. Inside that screen, I import form component. And inside form component I have a text input.
Then, I want to simulate text input on change text, but always get an error
Method “simulate” is meant to be run on 1 node. 0 found instead.
This is my test file
it('calls the login submit method', () => {
const fieldPhoneNumber = wrapper
.find('Form')
.dive()
.find('TextInput[id="fieldPhoneNumber"]');
fieldPhoneNumber
.at(0)
.simulate('changeText', { target: { value: '082262366193' } });
});
This is my component login file
import React, { useState, useEffect } from 'react';
import { ScrollView, StatusBar, Platform } from 'react-native';
import Header from './components/Header';
import Form from './components/Form';
import ButtonSocialMedia from './components/ButtonSocialMedia';
function LoginScreen() {
const [phoneNumber, setPhoneNumber] = useState('');
const [focus, setFocus] = useState(false);
useEffect(() => {
}, [phoneNumber]);
const changePhoneNumber = (value) => {
setPhoneNumber(value);
};
const showAppleButton = () => {
if (Platform.OS === 'ios') {
const version = Platform.Version.split('.')[0];
if (version >= 13) {
return true;
} else {
return false;
}
} else {
return false;
}
};
const loginSubmit = () => {
console.log('Login Submit');
};
return (
<ScrollView>
<StatusBar
translucent
backgroundColor="transparent"
barStyle="light-content"
/>
<Header />
<Form
phoneNumber={phoneNumber}
changePhoneNumber={(value) => changePhoneNumber(value)}
focus={focus}
setFocus={() => setFocus(true)}
loginSubmit={() => loginSubmit()} />
<ButtonSocialMedia showAppleButton={() => showAppleButton()} />
</ScrollView>
);
}
export default LoginScreen;
This is my form component
/* eslint-disable prettier/prettier */
import React from 'react';
import { View, Text, TextInput } from 'react-native';
import styles from '../styles/StyleForm';
import color from '../../../../__global__/styles/themes/colorThemes';
import regex from '../../../../constant/regex';
import * as yup from 'yup';
import { Formik } from 'formik';
import ButtonFull from '../../../../__global__/button/buttonFull';
const regexPhoneNumber = regex.phone;
function Form(props) {
const renderFocus = () => {
if (props.focus) {
return (
<Text style={styles.textFocus}>Type your phone number</Text>
);
}
};
return (
<Formik
enableReinitialize={true}
initialValues={{
phoneNumber: props.phoneNumber,
}}
onSubmit={values => {
console.log('Login Submit');
}}
validateOnMount={true}
validationSchema={yup.object().shape({
phoneNumber: yup
.string()
.required()
.min(8)
.matches(regexPhoneNumber, 'Phone number is not valid'),
})}>
{({
// values,
handleChange,
errors,
setFieldTouched,
touched,
isValid,
handleSubmit,
}) => (
<View style={styles.form}>
<View style={styles.subContainer}>
<View style={styles.containerTitle}>
<Text style={styles.textTitle}>+62</Text>
</View>
<View style={styles.containerPhoneNumber}>
{renderFocus()}
<TextInput
id={'fieldPhoneNumber'}
onFocus={() => props.setFocus(true)}
value={props.phoneNumber}
style={styles.subContainerPhoneNumber}
placeholderStyle={styles.placeholder}
placeholder={'Type your phone number'}
onChangeText={(value) => {
handleChange('phoneNumber');
props.changePhoneNumber(value);
setFieldTouched('phoneNumber', true);
}}
keyboardType={'numeric'}
onBlur={() => setFieldTouched('phoneNumber', true)}
/>
</View>
</View>
{touched.phoneNumber && errors.phoneNumber && (
<View style={styles.containerError}>
<Text style={styles.textError}>Phone number is not valid</Text>
</View>
)}
<View style={styles.containerButton}>
<ButtonFull
isDisabled={!isValid}
id={'buttonLogin'}
color={isValid ? color.thema : color.grey}
handleSubmit={() => props.loginSubmit()}
title={'Next'}
/>
</View>
</View>
)}
</Formik>
);
}
export default Form;
The error you're facing implies that the statement const fieldPhoneNumber wrapper.find('Form').dive().find('TextInput[id="fieldPhoneNumber"]'); couldn't find the TextInput component and hence the simulate function cannot be called. Try searching for the string "TextInput" inside the wrapper, and see if that works.
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 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.
hii i'm still new to react-native i'm trying to implement a search in the listview without Refetching the data (search by name )
like searching the json file i first fetched so
how can i make it works guys ? i followed many tutorials but still can't do it the listView is allways empty
here's my code i hope i found a solution for that
import React, { Component } from "react";
import { View, Text, Image, ListView } from "react-native";
import axios from "axios";
import { SearchButton } from "./utilities/SearchButton";
import SearchBar from "react-native-searchbar";
class SearchScreen extends Component {
constructor(props) {
super(props);
this.ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.state = {
doctors: [],
specefic: []
};
}
componentWillMount() {
this.fetchdata();
}
fetchdata = () => {
axios
.get("http://localhost:3000/api/Doctor")
.then(response => this.setState({ doctors: response.data }));
};
static navigationOptions = ({ navigation }) => {
return {
headerRight: <SearchButton navigation={navigation} />
};
};
render() {
return (
<View>
<View>
<SearchBar
ref={ref => (this.props.navigation.searchBar = ref)}
data={this.state.doctors}
handleResults={results => {
this.setState({ specefic: results });
}}
iOSPadding={false}
allDataOnEmptySearch={true}
fontSize={23}
hideBack={true}
heightAdjust={-5}
/>
</View>
<View>
<ListView
enableEmptySections={true}
dataSource={this.ds.cloneWithRows(this.state.doctors)}
renderRow={service => {
return (
<View style={styles.box}>
<Image
style={styles.image}
source={{ uri: service.profileImageUrl }}
/>
<View style={styles.boxContent}>
<Text style={styles.title}>{service.nom}</Text>
<Text style={styles.description}>{service.email}</Text>
</View>
</View>
);
}}
/>
</View>
</View>
);
}
}
export default SearchScreen;
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!');
}}
/>
)
)}