Rendering a map with an API in react native - reactjs

I am currently trying to build a currency convertor with react native but I cannot seem to render this API data back as a map and then into a rate. If anyone can guide me I would really appreciate. I would like to use the API response as a number and mathematically calculate input and provide out which is the users input converted.
The error Im getting is that undefined is not a function(near '...this.state.rates.map...')
The commented out part is to be ignored.
The following is my code:
import React from 'react';
import {AppRegistry, Text, View, Alert} from 'react-native';
const rate = 'https://free.currencyconverterapi.com/api/v6/convert?q=GBP_PKR&compact=ultra&apiKey=59825436b2556cde7205';
class Rates extends React.Component {
constructor(props) {
super(props);
this.state = {
rates: 1,
}
}
componentDidMount() {
fetch('https://free.currencyconverterapi.com/api/v6/convert?q=GBP_PKR&compact=ultra&apiKey=59825436b2556cde7205')
.then(res => res.json())
.then(data => this.setState({rates: data.rate}));
console.log(rates.data);
}
render() {
const rateNumber = this.state.rates.map(function(listItem, index){
<View key={index}>
<Text> {listItem.rate}</Text>
</View>
});
return (
<View>
<Text>Rate for today </Text>
<Text>{rateNumber}</Text>
</View>
)
}
}
export default Rates;
App
import React from 'react';
import {ListView, StyleSheet,RefreshControl, Text,TextInput, TouchableOpacity, View } from 'react-native';
import Rates from './data/Rates';
class App extends React.Component {
constructor(props){
super(props);
this.state = {
gbp: '',
pkr: '',
rate: [],
}
}
/*componentDidMount() {
fetch('https://free.currencyconverterapi.com/api/v6/convert?q=GBP_PKR&compact=ultra&apiKey=59825436b2556cde7205')
.then(response => response.json())
.then(data=> this.setState({ rate: [data] }));
}
*/
convert() {
const gbp = this.state.gbp;
const pkr = this.state.pkr;
const total = gbp*pkr;
console.log(total);
}
render() {
const rate = this.state;
const amount = JSON.stringify(rate.rate);
return (
<View style={styles.container}>
<View>
<Text>{amount}</Text>
<View><Rates/></View>
</View>
<TextInput
placeholder="GBP"
value={this.state.gbp}
onChangeText={(gbp) => this.setState({ gbp })}
keyboardType='numeric'
/>
<TextInput
placeholder="PKR"
value={this.state.pkr}
onChangeText={(pkr) => this.setState({ pkr })}
keyboardType='numeric'
/>
<TouchableOpacity onPress={() =>this.refreshDataFromServer()}>
<Text value={this.state.rate}> Convert</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
export default App;

Related

How to set the state from a button inside static navigationoption react-native?

I want to click the TouchableOpacity and set the state true so that will open. I am getting error. and how to align the button in center at the header? alignSelf is not working.
`
import React, {Component} from 'react';
import {
StyleSheet,
SafeAreaView,
View,
TouchableOpacity,
Text,
} from 'react-native';
import Menu from '../../src/components/menubar';
export default class SearchPage extends Component {
constructor(props) {
super(props);
this.state = {isMenubarDisplayed: false};
}
static navigationOptions = {
headerTitle: () => {
return (
<TouchableOpacity
onPress={()=> this.setState({isMenubarDisplayed: true})}>
<Icon name="search" size={20} color="#000" />
</TouchableOpacity>
);
},
headerTitleStyle: {
alignSelf: 'center',
flex: 1,
},
};
render() {
return (
<SafeAreaView style={styles.container}>
{this.state.isMenubarDisplayed ? (
<Menu />
) : null}
</SafeAreaView>
);
}
}`
You need to try this, expo-snack .
This is my below code for search.js ,
import * as React from 'react';
import { Text, View, StyleSheet,TouchableOpacity } from 'react-native';
import Constants from 'expo-constants';
import Menu from './menu';
import Icon from 'react-native-vector-icons/FontAwesome';
export default class Search extends React.Component {
constructor(props){
super(props);
this.state={
isMenubarDisplayed: false,
}
}
static navigationOptions = ({ navigation }) => {
return {
headerTitle: () => {
return (
<TouchableOpacity onPress={navigation.getParam('toggleMenu')}>
<Icon name="search" size={20} color="#000" />
</TouchableOpacity>
);
},
};
};
toggleMenu = () => {
this.setState({ isMenubarDisplayed: !this.state.isMenubarDisplayed})
}
renderMenu = () => (
<Menu />
)
componentDidMount(){
this.props.navigation.setParams({
toggleMenu: this.toggleMenu
});
}
render() {
return (
<View style={styles.container}>
{this.state.isMenubarDisplayed?this.renderMenu():<View></View>}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
Hope it helps. feel free for doubts.
This is all you need https://reactnavigation.org/docs/en/header-buttons.html#header-interaction-with-its-screen-component
static navigationOptions = ({ navigation }) => {
return {
headerTitle: () => {
return (
<View style={{ flex: 1, alignItems: 'center' }}>
<TouchableOpacity onPress={navigation.getParam('toggleMenu')}>
<Icon name="search" size={20} color="#000" />
</TouchableOpacity>
</View>
);
},
};
};
componentDidMount() {
this.props.navigation.setParams({ toggleMenu: this.toggleMenu });
}
toggleMenu = () => {
this.setState({isMenubarDisplayed: true});
}

How can I make componentDidMount render again?

I'm fetching api(makeup API) in Explore component and using it also in Explorebutton.
Im taking brands as a button in ExploreButtons. When i click button in FlatList element in ExploreButtons I want to see images from api in second FlatList in ExploreButtons. Is there a way componentDidMount can rerender when i click button?
import React, { Component } from 'react'
import { View } from 'react-native'
import ExploreButtons from './ExploreButtons'
export default class Explore extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
makeupApi: 'http://makeup-api.herokuapp.com/api/v1/products.json',
}
}
callbackFunction = (item) => {
this.setState({
makeupApi: 'http://makeup-api.herokuapp.com/api/v1/products.json?brand=' + item,
})
}
async componentDidMount() {
try {
const response = await fetch(this.state.makeupApi);
const responseJson = await response.json();
this.setState({
isLoading: false,
dataSource: responseJson,
}, function () {
});
const reformattedArray = this.state.dataSource.map(obj => {
var rObj = {};
rObj = obj.brand;
return rObj;
});
this.setState({
duplicatesRemoved: reformattedArray.filter((item, index) => reformattedArray.indexOf(item) === index)
})
}
catch (error) {
console.error(error);
}
};
render() {
console.log(this.state.makeupApi)
return (
<View style={{ flex: 1 }}>
<ExploreButtons
api={this.state.dataSource}
removedDuplicatesFromAPI={this.state.duplicatesRemoved}
parentCallback={this.callbackFunction}
makeupApi= {this.state.makeupApi} />
</View>
)
}
}
export default class ExploreButtons extends Component {
getBrandImages = (item) => {
this.props.parentCallback(item)
}
render() {
return (
<View style={{ flex: 1 }}>
<View>
<FlatList
horizontal
showsHorizontalScrollIndicator={false}
data={this.props.removedDuplicatesFromAPI}
renderItem={({ item }) =>
<TouchableOpacity
style={styles.exploreButtons}
onPress={() => {
this.getBrandImages(item)
}}
>
<Text>{item}</Text>
</TouchableOpacity>
}
keyExtractor={item => item}
/>
</View>
<View>
<FlatList
data={this.props.api}
renderItem={({ item }) =>
<View>
<Image source={{ uri: item.image_link }}
style={{
alignSelf: "center",
width: '100%',
height: 300,
}} />
</View>
}
keyExtractor={item => item.id.toString()} />
</View>
</View>
)
}
}
You could just put all the logic inside componentDidMount on another function and call it when you call the callback. As a first very rough approach this would work:
Notes: you don't really need the API URL in the state, put the item on the state and construct the URL based on it.
import React, { Component } from 'react';
import { View } from 'react-native';
import ExploreButtons from './ExploreButtons';
export default class Explore extends Component {
API_URL = 'http://makeup-api.herokuapp.com/api/v1/products.json';
constructor(props) {
super(props);
this.state = {
isLoading: true,
item: null,
dataSource: null,
duplicatesRemoved: [],
};
}
getAPIURL(item) {
if(!item){
return API_URL
}
return `${API_URL}?brand=${item}`;
}
async fetchData(item) {
try {
const url = getAPIURL(item);
const response = await fetch(url);
const responseJson = await response.json();
this.setState({
isLoading: false,
dataSource: responseJson,
item,
});
const reformattedArray = responseJSON.map(({ brand }) => brand);
this.setState({
duplicatesRemoved: reformattedArray.filter(
(item, index) => reformattedArray.indexOf(item) === index,
),
});
} catch (error) {
console.error(error);
}
}
async componentDidMount() {
fetchData();
}
render() {
const { dataSource, duplicatesRemoved, item } = this.state;
return (
<View style={{ flex: 1 }}>
<ExploreButtons
api={dataSource}
removedDuplicatesFromAPI={duplicatesRemoved}
parentCallback={this.fetchData}
makeupApi={getURL(item)}
/>
</View>
);
}
}
export default class ExploreButtons extends Component {
getBrandImages = item => {
this.props.parentCallback(item);
};
render() {
const { removedDuplicatesFromAPI, api } = this.props;
return (
<View style={{ flex: 1 }}>
<View>
<FlatList
horizontal
showsHorizontalScrollIndicator={false}
data={removedDuplicatesFromAPI}
renderItem={({ item }) => (
<TouchableOpacity
style={styles.exploreButtons}
onPress={() => {
this.getBrandImages(item);
}}
>
<Text>{item}</Text>
</TouchableOpacity>
)}
keyExtractor={item => item}
/>
</View>
<View>
<FlatList
data={api}
renderItem={({ item }) => (
<View>
<Image
source={{ uri: item.image_link }}
style={{
alignSelf: 'center',
width: '100%',
height: 300,
}}
/>
</View>
)}
keyExtractor={item => item.id.toString()}
/>
</View>
</View>
);
}
}
How can I make componentDidMount render again?
Not sure what you mean, but I think what you are asking is How can I make componentDidMount *run* again?, and to do that, you would need to have the same code in callbackFunction to run that again. componentDidMount will only run after the first time the component render.
Also notice that if you want to rerender the FlatList you need to pass extraData so it know that it needs to rerender.

React native Tried to get frame for out of range index NaN (MYSQL database)

So I started learning react-native from videos and they have used ListView but as the ListView will be deprecated soon and will be removed. I get to know that FlatList will be the proper replacement but being a beginner I am not able to migrate to Flatlist.
Error message
ListView has been removed from React Native.See link for more information or use 'deprecated-react-native-listview'
import React, { Component } from 'react';
import {
Platform,
StyleSheet,
Text,
View,
FlatList,
ActivityIndicator,
} from 'react-native';
import { createStackNavigator, createAppContainer } from 'react-navigation';
export default class Login extends Component {
static navigationOptions= ({navigation}) =>({
header: null
});
state = {
username : [],
data : []
}
constructor(props) {
super(props);
this.state = {
isLoading: true, // check if json data (online) is fetching
dataSource: [], // store an object of json data
};
}
componentDidMount () {
return fetch("http://172.16.2.109:8090/assessment/getdata2.php?username=test2312")
.then((response) => response.json())
.then((responseJson) => {
// set state value
this.setState({
isLoading: false, // already loading
dataSource: responseJson
});
})
.catch((error) => {
ToastAndroid.show(error.toString(), ToastAndroid.SHORT);
});
}
render() {
const { navigate } = this.props.navigation;
if(this.state.isLoading) {
return(
<View style={{flex: 1, padding: 20}}>
<ActivityIndicator/>
</View>
)
}
return(
<View style={{flex: 1, paddingTop:20}}>
<FlatList
data={this.state.dataSource}
renderItem={({item}) => {
return (
<View>
<Text style={styles.info}>{item.ascendant} is </Text>
</View>
)
}}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
}
const styles = StyleSheet.create({
info: {
fontSize: 20,
}
});
Hope this helps!
import { FlatList } from 'react-native';
<FlatList
data={this.state.dataSource}
showsVerticalScrollIndicator={false}
renderItem={(rowData, index) => (
<Text style={styles.rowViewContainer} onPress={this.GetListViewItem.bind(this, rowData.fruit_name)}>{rowData.fruit_name}</Text>
<View
style={{
height: .5,
width: "100%",
backgroundColor: "#000",
}}
/>
)}
keyExtractor={(item, index) => index.toString()}
style={{marginTop: 10}}
/>
try this code
import { AppRegistry, StyleSheet, FlatList, Text, View, Alert, ActivityIndicator, Platform} from 'react-native';
class Project extends Component {
constructor(props)
{
super(props);
this.state = {
isLoading: true
}
}
componentDidMount() {
return fetch('https://reactnativecode.000webhostapp.com/FruitsList.php')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson
}, function() {
// In this block you can do something with new state.
});
})
.catch((error) => {
console.error(error);
});
}
FlatListItemSeparator = () => {
return (
<View
style={{
height: 1,
width: "100%",
backgroundColor: "#607D8B",
}}
/>
);
}
GetFlatListItem (fruit_name) {
Alert.alert(fruit_name);
}
render() {
if (this.state.isLoading) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
return (
<View style={styles.MainContainer}>
<FlatList
data={ this.state.dataSource }
ItemSeparatorComponent = {this.FlatListItemSeparator}
renderItem={({item}) => <Text style={styles.FlatListItemStyle} onPress={this.GetFlatListItem.bind(this, item.fruit_name)} > {item.fruit_name} </Text>}
keyExtractor={(item, index) => index}
/>
</View>
);
}
}
const styles = StyleSheet.create({
MainContainer :{
justifyContent: 'center',
flex:1,
margin: 10,
paddingTop: (Platform.OS === 'ios') ? 20 : 0,
},
FlatListItemStyle: {
padding: 10,
fontSize: 18,
height: 44,
},
});
AppRegistry.registerComponent('Project', () => Project);

React Native data not updating when firebase updated

I am working on a project, I have done it 99%, but there's a problem.
Why the data doesn't change when I updated it?
In firebase the data has changed but my list still displays the previous data.
This is my code:
import React from 'react';
import { StyleSheet, Text, View, ListView,TouchableOpacity,Image,Alert,ActivityIndicator } from 'react-native';
import { Button, List, ListItem } from 'native-base'
import firebase from '../routes/set';
var data = []
export default class Menu extends React.Component {
constructor(props) {
super(props);
this.ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 })
this.state = {
listViewData: data,
loading:false
}
}
componentWillMount() {
var that = this
firebase.database().ref('/user').on('child_added', function (data) {
var newData = [...that.state.listViewData]
newData.push(data)
that.setState({ listViewData: newData })
})
}
async deleteRow(data,secId, rowId, rowMap) {
await firebase.database().ref('user/' + data.key).set(null)
rowMap[`${secId}${rowId}`].props.closeRow();
var newData = [...this.state.listViewData];
newData.splice(rowId, 1)
this.setState({ listViewData: newData });
}
render() {
return (
this.state.listViewData<1?<ActivityIndicator/>:
<View style={styles.container}>
<List
enableEmptySections
dataSource={this.ds.cloneWithRows(this.state.listViewData)}
renderRow={(data,secId,rowId,rowMap)=>
<ListItem>
<TouchableOpacity onLongPress={()=>
Alert.alert(null,'data',
[
{text:'delete',onPress:()=>this.deleteRow( data,secId, rowId, rowMap)},
{text:'edit',onPress:()=>this.props.navigation.navigate('Edit',{data:data,secId:secId,rowId:rowId,rowMap:rowMap})},
{text:'cancel',onPress:()=>null}
])
}>
<View style={{flexDirection:'row', marginLeft:20,marginTop:20}}>
<Image source={data.val().avatarSource} style={{width:50,height:50,borderRadius:30}}/>
<View style={{flexDirection:'column',marginLeft:20}}>
<Text> {data.val().name}</Text>
<Text> {data.val().age}</Text>
<Text> {data.val().gender}</Text>
<Text> {data.val().dob}</Text>
</View>
</View>
</TouchableOpacity>
</ListItem>
}
renderRightHiddenRow={( secId, rowId, rowMap,data) =>
<Button full danger onPress={() => this.deleteRow(secId, rowId, rowMap, data)}>
</Button>
}
/>
<TouchableOpacity onPress={()=>this.props.navigation.navigate('Daftar')}>
<Text>Insert</Text>
</TouchableOpacity>
</View>
);
}m
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});

Add data in React Native and Save the State

I'm building a basic React Native application where the user enters his name in a TextInput and on button press his name along with his image is added to a ScrollView in another View. Images are stored and named in accord with the name of the person. Example - Name: 'ABC', the image fetched from assets will be 'ABC.jpg'.
I'm able to do this for one person, but every time I add a new entry the previous one gets overwritten. How can I retain the previous entry yet add another entry?
Home
import React, {Component} from 'react';
import { StyleSheet, Text, View, Image, ScrollView, Button, TouchableWithoutFeedback, TextInput} from 'react-native';
import { createStackNavigator } from 'react-navigation';
class HomeScreen extends React.Component {
render() {
const { navigation } = this.props;
const name0 = navigation.getParam('name0', 'NO-ID');
return (
<View style={styles.container}>
<ScrollView vertical={true} contentContainerStyle={{flexGrow: 1}}>
<Text style={styles.category}>Category 1</Text>
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}>
<TouchableWithoutFeedback onPress={() => {
this.props.navigation.navigate('Details', {
name: name0,
intro: 'lorem ipsum',
detail1: 'XYZ',
detail2: 'ABC',
});
}}>
<View style={styles.view}>
<Image source={require('./assets/rohit.jpg')} style={styles.image}></Image>
<Text style={styles.text}>{name0}</Text>
</View>
</TouchableWithoutFeedback>
</ScrollView>
</ScrollView>
</View>
)
}
}
Add Data
class AddData extends React.Component {
constructor(props) {
super(props);
this.state = { text: '' };
}
render() {
function onPressLearnMore() {
alert('Hi')
}
return (
<View style={{flex: 1, flexDirection: 'column', justifyContent:'center', alignItems: 'center'}}>
<TextInput
style={{height: 40,width:200}}
onChangeText={(text) => this.setState({input: text})}
/>
<Button
onPress={() => {
this.props.navigation.navigate('Home', {
name0: this.state.input
});
}}
title="Pass Data"
/>
</View>
);
}
}
Navigator
const RootStack = createStackNavigator(
{
Home: HomeScreen,
Details: DetailsScreen,
Data: AddData
},
{
initialRouteName: 'Data',
}
);
export default class App extends React.Component {
render() {
return <RootStack />;
}
}
Screen 1
Screen 2
Because you aren't saving your data from the user input anywhere, simply passing it back to home, it is getting overwritten and as your code sits, it is working! ;)
To simply put a bandaid on what you're doing, because this method of passing data is slightly taboo.. and you cannot achieve what you're asking without some sort of state persistence like AsyncStorage, or... to not have two screens. Because everytime React re-renders your screen any data that was in your state object disappears...
Inside your home component, on did mount you could store user input to state as objects inside an array. IN the below example I am using the user data you are sending back to home, however this would need to be data retrieved from some sort of persistence.
class HomeScreen extends React.Component {
constructor(props) {
super(props)
this.state = {
usersArray: []
}
}
componentDidMount() {
let userInfo = this.props.navigation.getParam('name0', 'NO-ID');
userInfo !== undefined ? this.setState({usersArray:
[...this.state.usersArray, userInfo]}) : console.log('NO USER DATA to add')
}
render() {
const { navigation } = this.props;
const name0 = navigation.getParam('name0', 'NO-ID');
return (
<View style={styles.container}>
<ScrollView vertical={true} contentContainerStyle={{flexGrow: 1}}>
<Text style={styles.category}>Category 1</Text>
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}>
<TouchableWithoutFeedback onPress={() => {
this.props.navigation.navigate('Details', {
name: name0,
intro: 'lorem ipsum',
detail1: 'XYZ',
detail2: 'ABC',
});
}}>
<View style={styles.view}>
<Image source={require('./assets/rohit.jpg')} style={styles.image}></Image>
{/* <Text style={styles.text}>{name0}</Text> */}
{ this.state.usersArray.map((ele, index) => {
return <Text key={index} style={styles.text}>{ele.name}</Text>
})}
</View>
</TouchableWithoutFeedback>
</ScrollView>
</ScrollView>
</View>
)
}
}
Please keep in mind you may need to change your user data to be objects for this rest and spreading to work.

Resources