React Native : Show parent component after closing a modal on child component - reactjs

I have two components a Parent Component (App.js) and a Child Component (Logitem.js)
The parent component contains an array of child component and passes props to it.
The child component has a modal which is invoked upon clicking on a text element inside the child component.
The modal has a delete button which performs a delete operation on db.
All the above are working fine.
After the delete operation is performed I would like to show the parent component (aka) App.js but as of now the UI is still showing the modal on the child component.
How do we achieve that ?
Parent Component
import React, { Component } from 'react';
import { StyleSheet, View, Text, ScrollView, Modal, DatePickerIOS } from 'react-native';
import {
dropLogsTable,
createLogsTable,
getProfileHeightStandardfromDB,
saveLogsRecord,
populateDummyLogs,
getLogsRecords,
getLogsRecordsFromDB,
neverendingmethod,
getLogsDetailsforSaveDelete
} from '../src/helper';
import { Spinner } from '../src/Spinner';
import Logitem from '../src/Logitem';
export default class App extends Component {
state = {
allLogs:{
rows:{
_array:[{logstringdate:''}]
}
},
profileobject: {profileheight: 100, profilestandard: "XYZ"},
showspinner: true,
count:0
};
componentDidMount() {
this.fetchProfileData();
this.getAllLogs();
}
renderSpinner() {
if(this.state.showspinner) {
return <Spinner size="small" />;
}
else {
//return this.state.allLogs.rows._array.map(ae => <Text>{ae.bmi}</Text>);
return this.state.allLogs.rows._array.map(
(ae) => (
<View
key={ae.logdate}
>
<Logitem
logstringdate={ae.logstringdate}
bmi={ae.bmi}
weight={ae.metricweight}
logdate={ae.logdate}
/>
</View>
)
);
}
}
async fetchProfileData() {
console.log('Before Profile Fetch');
const result = await getProfileHeightStandardfromDB();
console.log('After Profile Fetch');
console.log('Height : '+result.profileheight);
console.log('Standard: '+result.profilestandard);
this.setState({profileobject:result}); //Line Y
return result; //Line X
}
async getAllLogs() {
console.log('Before All Logs Fetch');
const allLogs = await getLogsRecordsFromDB();
console.log('After All Logs Fetch');
console.log('Spinner State ==>'+this.state.showspinner);
if(allLogs != null)
{
this.setState({allLogs, showspinner: false});
console.log('After async spinner state ==>'+this.state.showspinner);
console.log(allLogs);
}
return allLogs;
}
render() {
return (
<ScrollView style={styles.container}>
{this.renderSpinner()}
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
top: {
width: '100%',
flex: 1,
},
bottom: {
flex: 1,
alignItems: 'center',
},
});
Child Component
import React, { Component } from 'react';
import { Text, View, Modal, DatePickerIOS, TextInput, Button } from 'react-native';
import {
deleteSelectedRecordDB
} from '../src/helper';
import { Spinner } from '../src/Spinner';
export default class Logitem extends Component {
constructor(props) {
super(props);
const { logstringdate, bmi, weight, logdate } = this.props;
}
state = {
selecteddate: '1',
selectedweight: this.props.weight,
showmodal: false,
date: new Date(86400000 * this.props.logdate),
}
async deleteSelectedRecord(){
console.log('Delete clicked');
console.log('this.state.selecteddate ==>' + this.state.selecteddate); //LINE X
const result = await deleteSelectedRecordDB(this.props.logdate);
console.log('deleteSelectedRecord after');
console.log('result ==> '+ result);
return result;
}
setModalVisible = (visible) => {
this.setState({showmodal: visible});
}
onWeightClick = () => {
this.setState({ selecteddate: this.props.logdate, showmodal: true }, () => {
console.log('Value in props==>' + this.props.logdate);
console.log('The selecteddate in the state ==> ' + this.state.selecteddate);
});
}
onDateChange(date) {
this.setState({
date: date
});
}
render() {
return (
<View style={styles.containerStyle}>
<Modal
animationType="slide"
transparent={false}
visible={this.state.showmodal}
onRequestClose={() => {alert("Modal has been closed.")}}
>
<View style={{marginTop: 22}}>
<DatePickerIOS
date={this.state.date}
mode="date"
onDateChange={(date) => this.onDateChange(date)}
style={{ height: 100, width: 300 }}
/>
</View>
<View style={{ marginTop: 22, borderColor: '#ddd', borderWidth: 5 }}>
<TextInput
returnKeyType="done"
keyboardType='numeric'
style={{
height: 40,
width: 60,
borderColor: 'gray',
borderWidth: 1,
}}
onChangeText={(text) => this.setState({ selectedweight: text })}
value={this.state.selectedweight.toString()}
/>
<Text>KG</Text>
<Button
title="Delete"
onPress={this.deleteSelectedRecord.bind(this)}
style={{ marginTop: 200 }}
/>
</View>
</Modal>
<View style={styles.headerContentStyle}>
<Text>{this.props.logstringdate}</Text>
<Text>{this.props.bmi}</Text>
</View>
<View style={styles.thumbnailContainerStyle}>
<Text onPress={this.onWeightClick}>{this.props.weight}</Text>
</View>
</View>
);
}
};
const styles = {
containerStyle: {
borderWidth: 1,
borderRadius: 2,
borderColor: '#ddd',
borderBottomWidth: 0,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2},
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 1,
marginLeft: 5,
marginRight: 5,
marginTop:10,
},
thumbnailContainerStyle: {
justifyContent: 'center',
alignItems: 'center',
marginLeft: 10,
marginRight: 10,
flexDirection: 'row'
},
headerContentStyle: {
flexDirection: 'column',
justifyContent: 'space-around'
},
};

-Simple in deleteSelectedRecord function call setModalVisible(false)

Related

How do I access a component's inner method using hooks in react-native

I am using the this, and am trying to use the "close" method. How might I call that close method once I hit a cancel alert as shown below? I believe this is my general misunderstanding of how react hooks/classes work with public methods. Any help would be appreciated!
<Swipeable
renderRightActions={renderRightActions}
onSwipeableRightOpen={() => {
Alert.alert(
"Delete " + title + "?",
"",
[
{
text: "Cancel",
onPress: () => {this.close()},
style: "cancel",
},
{ text: "Delete", onPress: () => removeItem(id) },
],
{ cancelable: true }
);
}}
leftThreshold={0}
rightThreshold={150}>Random stuff in here</Swipeable>
EDIT I rewrote it as a class:
import React from "react";
import { StyleSheet, Alert, Text, View, TouchableWithoutFeedback, Animated } from "react-native";
import { Ionicons } from "#expo/vector-icons";
import Swipeable from "react-native-gesture-handler/Swipeable";
import * as Haptics from "expo-haptics";
class ListItem extends React.Component {
constructor(props) {
super(props);
const { id, title, onClick, drag, isActive, removeItem } = this.props;
this.id = id;
this.title = title;
this.onClick = onClick;
this.drag = drag;
this.isActive = isActive;
this.removeItem = removeItem;
}
componentDidUpdate = () => {
if (this.isActive) {
// haptic feedback
Haptics.impactAsync();
}
};
renderRightActions = (progress, dragX) => {
const trans = dragX.interpolate({
inputRange: [0, 50, 100, 101],
outputRange: [-20, 0, 0, 1],
});
return (
<View style={{ width: 45, marginTop: 20, marginLeft: 1000 }}>
<Animated.Text
style={{
transform: [{ translateX: trans }],
}}></Animated.Text>
</View>
);
};
onCancel() {
console.log("WHAT");
console.log(this);
}
render() {
return (
<Swipeable
renderRightActions={this.renderRightActions}
onSwipeableRightOpen={function () {
Alert.alert(
"Delete " + this.title + "?",
"",
[
{
text: "Cancel",
onPress: this.onCancel(),
style: "cancel",
},
{ text: "Delete", onPress: () => removeItem(id) },
],
{ cancelable: true }
);
}}
leftThreshold={0}
rightThreshold={150}>
<TouchableWithoutFeedback
onPress={function () {
this.onClick();
}}
onLongPress={this.drag}
delayLongPress={200}>
<View style={listItemStyles.item}>
<View
style={{
marginTop: 7,
marginLeft: 18,
}}>
<Text style={listItemStyles.itemTitle}>{this.title}</Text>
</View>
<View style={listItemStyles.itemIcon}>
<Ionicons
name={"ios-arrow-forward"}
size={30}
style={{ marginBottom: -3 }}
color={"#E3E3E3"}
/>
</View>
</View>
</TouchableWithoutFeedback>
</Swipeable>
);
}
}
export default ListItem;
// Styles
const listItemStyles = StyleSheet.create({
// list item
item: {
padding: 3,
marginVertical: 2,
marginLeft: "2%",
marginRight: "2%",
width: "96%",
// internals
flexDirection: "row",
justifyContent: "space-between",
},
itemTitle: {
flexDirection: "row",
justifyContent: "flex-start",
color: "#333333",
fontSize: 18,
},
itemSubtitle: {
fontSize: 12,
marginLeft: 5,
},
itemIcon: {
marginTop: 2,
marginRight: 10,
flex: 1,
flexDirection: "row",
justifyContent: "flex-end",
},
});
However when I print out (this), "close" does not show up as a method.
You cannot use this because it references your outer component due to your fat-arrow functions.
The docs say Using reference to Swipeable it's possible to trigger some actions on it. close: method that closes component.
The problem here is that the great thing about the fat arrow function (it does not has its own refernce, so the outer is used).
Here in your case, that means your outer component function that render the Swipeable component.
So for that use case, you need to use a class component for the onSwipeableRightOpen prop.

accessing this.props.navigation.state.params ouside render

I am passing a parameter from ListView to a detailed page. Can I access this parameter outside the render on my detailed page:
Below is my code to pass the parameter:
<ListView
dataSource={this.state.dataSource}
renderRow = {( rowData ) =>
<TouchableOpacity style = { styles.item } activeOpacity = { 0.4 } onPress = { this.clickedItemText.bind( this, rowData ) }>
<Text style = { styles.text }>{ rowData.ser }</Text>
</TouchableOpacity>
}
renderSeparator = {() =>
<View style = { styles.separator }/>
}
enableEmptySections = { true }
/>
</View>
clickedItemText( clickedItem )
{
this.props.navigation.navigate('Item', { item: clickedItem });
}
I can get the parameter value on my detailed page using the code below:
<View style = { styles.container2 }>
<Text style = { styles.text }>You Selected: { this.props.navigation.state.params.item.Location.toUpperCase() }</Text>
</View>
I need to do this.props.navigation.state.params.item.Location ouside render. The reason I want to do that because I want to create a ListView on my detailed page by filtering my Json data based on the passed parameter so for e.g. if the parameter passed is 2 then I want to filter my JSON data based on 2 and create another ListView.
As far as I know filtering of the JSON data can only be done outside render. I could be wrong though, I am new to react Native.
Below is my entire class for detailed page.
import React, { Component } from 'react';
import { StyleSheet, Text, View, ListView, ActivityIndicator, TextInput } from 'react-native';
import ServiceDetails from '../reducers/ServiceDetails';
class ServiceListDetails extends Component
{
constructor() {
super();
var newList = ServiceDetails.filter(obj => obj.fk === this.props.navigation.state.params.item.id);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: ds.cloneWithRows(ServiceDetails),
};
}
static navigationOptions =
{
title: 'SecondActivity',
};
ListViewItemSeparator = () => {
return (
<View
style={{
height: .5,
width: "100%",
backgroundColor: "#000",
}}
/>
);
}
render()
{
return(
<View style={styles.MainContainer}>
<ListView
dataSource={this.state.dataSource}
renderSeparator= {this.ListViewItemSeparator}
renderRow={(rowData) => <Text>{rowData.addr}</Text>}
/>
</View>
);
}
}
const styles = StyleSheet.create(
{
MainContainer:
{
justifyContent: 'center',
flex:1,
margin: 10
},
TextStyle:
{
fontSize: 23,
textAlign: 'center',
color: '#000',
},
rowViewContainer:
{
fontSize: 17,
paddingRight: 10,
paddingTop: 10,
paddingBottom: 10,
},
ActivityIndicator_Style:
{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
left: 0,
right: 0,
top: 0,
bottom: 0,
},
TextInputStyleClass:{
textAlign: 'center',
height: 40,
borderWidth: 1,
borderColor: '#009688',
borderRadius: 7 ,
backgroundColor : "#FFFFFF"
}
});
export default ServiceListDetails;
Below is screen shot of the error that I am getting on detailed page.
Below is the entire code of my Master Page that has a list View on it and it works fine:
import React, { Component } from 'react';
import { Text, View, StyleSheet, ListView, ActivityIndicator, TextInput, TouchableOpacity } from 'react-native';
import { Provider, connect } from 'react-redux';
import { createStore } from 'redux'
import reducers from '../reducers/ServiceReducer';
import ServiceItem from './ServiceItem';
import Icon from 'react-native-vector-icons/EvilIcons';
import ServiceDetail from './ServiceDetail';
import TestActivity from './TestActivity';
import { StackNavigator } from 'react-navigation';
import ServiceListDetails from './ServiceListDetails' ;
class AutoCompActivity extends Component {
constructor(props) {
super(props);
this.state = {
// Default Value of this State.
Loading_Activity_Indicator: true,
text:'',
}
this.arrayholder=[];
}
componentDidMount() {
const data = require('../reducers/services.json')
let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.setState({
Loading_Activity_Indicator: false,
dataSource: ds.cloneWithRows(data),
}, function() {
// In this block you can do something with new state.
this.arrayholder = data ;
});
}
SearchFilterFunction(text){
const newData = this.arrayholder.filter(function(item){
const itemData = item.ser.toUpperCase()
const textData = text.toUpperCase()
return itemData.indexOf(textData) > -1
})
this.setState({
dataSource: this.state.dataSource.cloneWithRows(newData),
text: text
})
}
ListViewItemSeparator = () => {
return (
<View
style={{
height: .5,
width: "100%",
backgroundColor: "#000",
}}
/>
);
}
/*Navigate_To_Second_Activity=(ser)=>
{
//Sending the JSON ListView Selected Item Value On Next Activity.
this.props.navigation.navigate('Second', { JSON_ListView_Clicked_Item: ser });
}*/
clickedItemText( clickedItem )
{
this.props.navigation.navigate('Item', { item: clickedItem });
}
static navigationOptions =
{
title: 'MainActivity',
};
render()
{
if (this.state.Loading_Activity_Indicator) {
return (
<View style={styles.ActivityIndicator_Style}>
<ActivityIndicator size = "large" color="#009688"/>
</View>
);
}
return (
<View style={styles.MainContainer}>
<TextInput
style={styles.TextInputStyleClass}
onChangeText={(text) => this.SearchFilterFunction(text)}
value={this.state.text}
underlineColorAndroid='transparent'
placeholder="Search Here"
/>
<ListView
dataSource={this.state.dataSource}
renderRow = {( rowData ) =>
<TouchableOpacity style = { styles.item } activeOpacity = { 0.4 } onPress = { this.clickedItemText.bind( this, rowData ) }>
<Text style = { styles.text }>{ rowData.ser }</Text>
</TouchableOpacity>
}
renderSeparator = {() =>
<View style = { styles.separator }/>
}
enableEmptySections = { true }
/>
</View>
);
}
}
export default MyNewProject= StackNavigator(
{
First: {screen: AutoCompActivity},
Item: {screen: ServiceListDetails}
}
);
const styles = StyleSheet.create(
{
MainContainer:
{
justifyContent: 'center',
flex:1,
margin: 10
},
TextStyle:
{
fontSize: 23,
textAlign: 'center',
color: '#000',
},
rowViewContainer:
{
fontSize: 17,
paddingRight: 10,
paddingTop: 10,
paddingBottom: 10,
},
ActivityIndicator_Style:
{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
left: 0,
right: 0,
top: 0,
bottom: 0,
},
TextInputStyleClass:{
textAlign: 'center',
height: 40,
borderWidth: 1,
borderColor: '#009688',
borderRadius: 7 ,
backgroundColor : "#FFFFFF"
},
separator:
{
height: 2,
backgroundColor: 'rgba(0,0,0,0.5)'
},
item:
{
padding: 15
},
text:
{
fontSize: 18
},
});
My services.json is below
[
{
"id":0,
"ser": "Test Service",
"Location": "TestLoc",
"Phone1":"(999)-999-5050",
"SecondLoc": "TestLoc2",
"email":"test#test.com",
"sourceLat":"33.977806",
"sourceLong":"-117.373261",
"destLatL1":"33.613355",
"destLongL1":"-114.596569",
"destLatL2":"33.761693",
"destLongL2":"-116.971169",
"destAddr1": "Test Address, Test Drive",
"destAddr2": "Test Address2, Test Drive2",
"onlineURL":"",
"Phone2": "(900)-900-3333"
}
]
My ServiceDetails.json is below:
[
{
"id":"1",
"fk": "0",
"addr": "2Test addr",
"phone": "(951)-955-6200",
"LatL":"33.935547",
"Long2":"-117.191",
"Online": ""
},
{
"id":"2",
"fk": "0",
"addr": "testaddr21",
"phone": "(999)-999-9999",
"LatL":"33.977880",
"Long2":"-117.1234",
"Online": ""
}
]
How can I achieve this?

React Native : How to rerender the parent component from an event on the child component

I have two components.
Parent Component : App.js
Child Component : Logitem.js
Parent Component renders a list of Child Components.
Each child component has a text element and when the text element is clicked it displays a modal.
The modal has a delete button and it performs a delete operation.
All of this is working fine.
When I click on the delete button inside the modal I am setting a boolean variable to hide the modal which also works.
But the list shown (containing the array of child components) are not the latest i.e the deleted element still appears in the list.
Are there any ways to rerender the render() method of the parent component.
I have tried updating state of the parent component (count) via the child component but still no luck.
I believed that if the state of the parent component is changed the render() of the parent component will be called but this is not happening.
Can someone let me know as to what can be done here ?
Parent Component
import React, { Component } from 'react';
import { StyleSheet, View, Text, ScrollView, Modal, DatePickerIOS } from 'react-native';
import {
dropLogsTable,
createLogsTable,
getProfileHeightStandardfromDB,
saveLogsRecord,
populateDummyLogs,
getLogsRecords,
getLogsRecordsFromDB,
neverendingmethod,
getLogsDetailsforSaveDelete
} from '../src/helper';
import { Spinner } from '../src/Spinner';
import Logitem from '../src/Logitem';
export default class App extends Component {
state = {
allLogs:{
rows:{
_array:[{logstringdate:''}]
}
},
profileobject: {profileheight: 100, profilestandard: "XYZ"},
showspinner: true,
count:0
};
componentDidMount() {
this.fetchProfileData();
this.getAllLogs();
}
renderSpinner() {
if(this.state.showspinner) {
return <Spinner size="small" />;
}
else {
//return this.state.allLogs.rows._array.map(ae => <Text>{ae.bmi}</Text>);
return this.state.allLogs.rows._array.map(
(ae) => (
<View
key={ae.logdate}
>
<Logitem
logstringdate={ae.logstringdate}
bmi={ae.bmi}
weight={ae.metricweight}
logdate={ae.logdate}
incrementCount={() => this.setState({count: count+1)}
/>
</View>
)
);
}
}
async fetchProfileData() {
console.log('Before Profile Fetch');
const result = await getProfileHeightStandardfromDB();
console.log('After Profile Fetch');
console.log('Height : '+result.profileheight);
console.log('Standard: '+result.profilestandard);
this.setState({profileobject:result}); //Line Y
return result; //Line X
}
async getAllLogs() {
console.log('Before All Logs Fetch');
const allLogs = await getLogsRecordsFromDB();
console.log('After All Logs Fetch');
console.log('Spinner State ==>'+this.state.showspinner);
if(allLogs != null)
{
this.setState({allLogs, showspinner: false});
console.log('After async spinner state ==>'+this.state.showspinner);
console.log(allLogs);
}
return allLogs;
}
render() {
return (
<ScrollView style={styles.container}>
{this.renderSpinner()}
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
top: {
width: '100%',
flex: 1,
},
bottom: {
flex: 1,
alignItems: 'center',
},
});
Child Component
import React, { Component } from 'react';
import { Text, View, Modal, DatePickerIOS, TextInput, Button } from 'react-native';
import {
deleteSelectedRecordDB
} from '../src/helper';
import { Spinner } from '../src/Spinner';
export default class Logitem extends Component {
constructor(props) {
super(props);
const { logstringdate, bmi, weight, logdate } = this.props;
}
state = {
selecteddate: '1',
selectedweight: this.props.weight,
showmodal: false,
date: new Date(86400000 * this.props.logdate),
}
async deleteSelectedRecord(){
console.log('Delete clicked');
console.log('this.state.selecteddate ==>' + this.state.selecteddate); //LINE X
const result = await deleteSelectedRecordDB(this.props.logdate);
console.log('deleteSelectedRecord after');
console.log('result ==> '+ result);
if (result)
{
this.setState({ showmodal: false });
this.props.incrementCount();
}
return result;
}
setModalVisible = (visible) => {
this.setState({showmodal: visible});
}
onWeightClick = () => {
this.setState({ selecteddate: this.props.logdate, showmodal: true }, () => {
console.log('Value in props==>' + this.props.logdate);
console.log('The selecteddate in the state ==> ' + this.state.selecteddate);
});
}
onDateChange(date) {
this.setState({
date: date
});
}
render() {
return (
<View style={styles.containerStyle}>
<Modal
animationType="slide"
transparent={false}
visible={this.state.showmodal}
onRequestClose={() => {alert("Modal has been closed.")}}
>
<View style={{marginTop: 22}}>
<DatePickerIOS
date={this.state.date}
mode="date"
onDateChange={(date) => this.onDateChange(date)}
style={{ height: 100, width: 300 }}
/>
</View>
<View style={{ marginTop: 22, borderColor: '#ddd', borderWidth: 5 }}>
<TextInput
returnKeyType="done"
keyboardType='numeric'
style={{
height: 40,
width: 60,
borderColor: 'gray',
borderWidth: 1,
}}
onChangeText={(text) => this.setState({ selectedweight: text })}
value={this.state.selectedweight.toString()}
/>
<Text>KG</Text>
<Button
title="Delete"
onPress={this.deleteSelectedRecord.bind(this)}
style={{ marginTop: 200 }}
/>
</View>
</Modal>
<View style={styles.headerContentStyle}>
<Text>{this.props.logstringdate}</Text>
<Text>{this.props.bmi}</Text>
</View>
<View style={styles.thumbnailContainerStyle}>
<Text onPress={this.onWeightClick}>{this.props.weight}</Text>
</View>
</View>
);
}
};
const styles = {
containerStyle: {
borderWidth: 1,
borderRadius: 2,
borderColor: '#ddd',
borderBottomWidth: 0,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2},
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 1,
marginLeft: 5,
marginRight: 5,
marginTop:10,
},
thumbnailContainerStyle: {
justifyContent: 'center',
alignItems: 'center',
marginLeft: 10,
marginRight: 10,
flexDirection: 'row'
},
headerContentStyle: {
flexDirection: 'column',
justifyContent: 'space-around'
},
};
Move deleteSelectedRecord to the parent and update its state setState({ allLogs: [...] }) in there.
By doing that you trigger parent to re-render itself and the list should be updated again.
The dumbest Logitem is, the better. Think of how would you write test for it having to fake this delete action for example.
You're trying to increment count in the parent component but are not altering this.state.allLogs, which is what feeds the list. As you call incrementCounter, maybe pass the item that is being deleted upwards so that you can remove it from the array that feeds the list.
The only down side to this is that you might have an array on your hands that does not represent the actual state of the array in the DB. (data inconsistency)
So, then you could do the following: delete the item from the DB from the child component and then call this.props.notifiyParent (renamed incrementCounter) and in the parent where notifyParent is defined you can retrieve the value for this.state.allLogs and update the parent's state -> this will trigger a re-render and your parent component will now show the updated list.
Also, as #mersocarlin suggests it's better for the child component to be "dumb" in that it does not have to carry the logic of how the item is deleted. It just needs to call the delete method that the parent would pass down and the delete method would be defined in the parent. Also, this way all DB transactions are carried out from a single place (the parent)

Child component won't render from function

I am trying to render a child component using the function renderHowManyAlcohol()
The component LYDSelectNumber doesn't render, the correct props are being passed down, any ideas why this doesn't render?
render() {
return (
<Flexible>
<LYDSceneContainer
goBack={this.props.goBack}
subSteps={this.props.subSteps}>
<Flexible>
{this.renderHowManyAlcohol()}
</Flexible>
</LYDSceneContainer>
</Flexible>
);
}
renderHowManyAlcohol() {
return (
<View style={styles.HowManyAlcoholContainer}>
<Text>this renders</Text>
// this component below doesn't
<LYDSelectNumber
selectedNumberValue={this.props.selectedNumberValue}
onChangeNumber={this.props.onChangeNumber}
/>
</View>
);
}
LYDSelectNumber component, styles.container shows as a number '60', which is weird, I have the styles at the bottom of the page.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Picker, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import * as theme from '../../../theme';
const Item = Picker.Item;
export const SelectedNumber = ({ value = 0, displayPicker = () => {} } = {}) =>
<TouchableOpacity onPress={displayPicker}>
<View style={styles.centerWrap}>
<View style={styles.selectedNumberWrap}>
<Text style={styles.selectedNumberText}>
{value}
</Text>
</View>
</View>
</TouchableOpacity>;
class LYDSelectNumber extends Component {
static propTypes = {
onChangeNumber: PropTypes.func.isRequired,
numbersRange: PropTypes.array,
style: PropTypes.any,
selectedNumberValue: PropTypes.number,
};
static defaultProps = {
numbersRange: [18, 120],
style: undefined,
selectedNumberValue: undefined,
};
render() {
return (
<View style={styles.container}>
<SelectedNumber value={this.props.selectedNumberValue} />
<Picker
style={styles.picker}
selectedValue={this.props.selectedNumberValue}
onValueChange={this.props.onChangeNumber}>
{this._renderNumbers()}
</Picker>
</View>
);
}
_renderNumbers = () => {
const [firstNumber, lastNumber] = this.props.numbersRange;
let numbersItems = [];
let n = firstNumber;
while (n <= lastNumber) {
numbersItems.push(<Item key={n} label={`${n}`} value={n} />);
n++;
}
return numbersItems;
};
}
export default LYDSelectNumber;
const styles = StyleSheet.create({
container: {
flex: 3,
justifyContent: 'center',
alignItems: 'center',
},
picker: {
position: 'absolute',
top: 0,
width: 1000,
height: 1000,
},
centerWrap: {
alignItems: 'center',
},
selectedNumberWrap: {
width: theme.utils.responsiveWidth(40),
paddingBottom: 20,
borderBottomWidth: 2,
alignItems: 'center',
borderBottomColor: theme.colors.darkGranate,
},
selectedNumberText: {
...theme.fontStyles.selectedNumber,
},
});

undefined is not a function (evaluating 'fetch('apiurl')')

I am using below versions
react-native-router-flux ^3.39.1
react-native 0.44.0
I expecting that will call API which I am using with "fetch"
Have used componentDidMount but it's showing another error
undefined is not an object (evaluating 'this._component.getScrollableNode')
But I am getting below error outputs
Steps to reproduce
Create three scene using router flux (In my case App, Login, Home)
Use ScrollView for creating the Login.js Create a button
using TouchableHighlight after that call the fetch with a function
using onPress like onPress={ () => this.fetchData() }
Below code, I am using for App.js
import React, { Component } from 'react';
import {
View,
Text,
StyleSheet,
AsyncStorage,
} from 'react-native';
import Login from './components/Login'
import Register from './components/Register'
import Home from './components/Home'
import { Scene, Router, TabBar, Modal, Schema, Actions, Reducer, ActionConst } from 'react-native-router-flux'
const reducerCreate = params=>{
const defaultReducer = Reducer(params);
return (state, action)=>{
console.log("ACTION:", action);
return defaultReducer(state, action);
}
};
export default class App extends Component {
constructor(props, context) {
super(props, context);
this.state = {
logged: false,
loading: true,
};
};
componentWillMount(){
self = this;
AsyncStorage.getItem('token')
.then( (value) =>{
if (value != null){
this.setState({
logged: true,
loading: false,
});
}
else {
this.setState({
loading: false,
})
}
});
};
render() {
if (this.state.loading) {
return <View><Text>Loading</Text></View>;
}
return (
<Router>
<Scene hideNavBar={true} key="root">
<Scene key="logIn" component={Login} title="Login" initial={!this.state.logged}/>
<Scene key="regisTer" component={Register} title="Register"/>
<Scene key="home" component={Home} title="home" initial={this.state.logged}/>
</Scene>
</Router>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
And below code, using for Login.js
/* #flow */
import React, { Component } from 'react';
import {
View,
StyleSheet,
Image,
ScrollView,
TextInput,
Text,
TouchableHighlight,
Alert,
} from 'react-native';
import { Container, Content, InputGroup, Input, Icon, Item } from 'native-base';
import Button from 'react-native-button'
import {Actions} from 'react-native-router-flux'
import ResponsiveImage from 'react-native-responsive-image'
export default class Login extends Component {
constructor(props){
super(props)
this.state = {
email: '',
password: '',
data: '',
}
}
fetchData() {
fetch('http://allstariq.tbltechnerds.com/api/login/?username=andress&password=23434')
.then((response) => response.json())
.then((responseData) => {
this.setState({
data: responseData.movies,
});
})
.done();
}
render() {
return (
<View style={styles.container}>
<ScrollView>
<View style={ styles.logoContainer }>
<View style={{flexDirection: 'row',}}>
<ResponsiveImage
source={require('../assets/logo.png')}
initWidth="300"
initHeight="160" />
</View>
</View>
<View style={ styles.formContainer }>
<Item>
<Icon active name='mail' />
<Input
onChangeText={(text) => this.setState({email: text})}
value={this.state.email}
placeholder='Email'/>
</Item>
<Item>
<Icon active name='key' />
<Input
onChangeText={(text) => this.setState({password: text})}
value={this.state.password}
placeholder='Password'/>
</Item>
<TouchableHighlight
style={ styles.loginButton }
onPress={ () => this.fetchData() }>
<Text style={ styles.btnText}>Login</Text>
</TouchableHighlight>
</View>
<View style={ styles.bottomContainer }>
<Text style={ styles.cenText }>Dont worry if you haven't an account yet . . </Text>
<Text
style={ styles.blueText}
onPress={ Actions.regisTer }
>Register Now</Text>
</View>
</ScrollView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
logoContainer: {
flex: .5,
padding: 10,
justifyContent: 'center',
alignItems: 'center',
},
logoItem: {
width: null,
height: null,
resizeMode: 'cover',
},
formContainer: {
flex: 4,
padding: 10,
},
inputelm: {
marginBottom: 10,
backgroundColor: '#999',
borderWidth: 0,
fontSize: 20,
color: '#FFF',
fontFamily: 'AmaticSC-Bold',
},
loginButton: {
borderRadius: 3,
marginBottom: 20,
marginTop: 20,
paddingLeft: 10,
paddingRight: 10,
backgroundColor: '#2196f3',
elevation: 4,
},
signupButton: {
borderRadius: 3,
marginBottom: 20,
marginTop: 20,
paddingLeft: 10,
paddingRight: 10,
backgroundColor: '#7cb342',
elevation: 4,
},
btnText: {
textAlign: 'center',
color: '#FFF',
fontSize: 30,
lineHeight: 40,
},
blueText: {
textAlign: 'center',
color: '#2196f3',
fontSize: 20,
lineHeight: 40,
},
bottomContainer: {
flex: 1,
padding: 10,
},
cenText: {
textAlign: 'center',
fontSize: 16,
},
});
What is the actual way to use fetch with react-native-router-flux? I am new to react, please help me.
well, its a couple of months late but the problem here is that your fetchData method hasn't got access to this because when you declare a method like this:
fetchData() {
}
Under the hood react is creating a function this way:
this.fetchData = function() {
}
when you declare a function using the function keyword, everything between {} will have its own "this" and your method will not have access to the "this" of the component context.
That is the reason why you are getting the "undefined", because you are calling "this.setState" inside the promise returned by fetch, so the error is not fetch, but this.
To solve this issue you could just define your method this way:
fetchData = () => {
}
And because the functions defined with a fat arrow do not create their own scope, the component "this" will be available from inside your method.
Did you try maybe to import the library?
Import fetch from "fetch";
You have not imported the fetch function, import it from the node-fetch module like
import fetch from 'node-fetch'

Resources