React Native Fetch works only on second click - reactjs

I do app on React Native. The problem is next :
OnPress some element i am trying make request to my server and log answer.
When I click button first time - my log do not show anything , but on second click I see response in the console.
import React from 'react'
import { View, Text, StyleSheet, TextInput , TouchableNativeFeedback , AsyncStorage } from 'react-native'
import {connect} from 'react-redux'
import * as pageActions from '../action/index'
import { bindActionCreators } from 'redux'
import api from '../api'
class Login extends React.Component {
constructor(props){
super(props);
this.state = {
loginAttempt : false,
email : 'admin#mail.ru',
password : 'password',
token : ''
}
}
loginAttempt() {
let email = this.state.email;
let password = this.state.password;
api.login(email,password)
}
render() {
return (
<View style={styles.container}>
<TextInput
placeholder="Email"
style={{height: 50, borderColor: 'gray', borderWidth: 1}}
onChangeText={(value) => this.setState({email : value})}
/>
<TextInput
placeholder="Password"
style={{height: 50, borderColor: 'gray', borderWidth: 1}}
onChangeText={(value) => this.setState({password : value})}
/>
<TouchableNativeFeedback
style={!this.state.loginAttempt ? {'opacity' : 1} : {'opacity' : 0}}
onPress={() => this.loginAttempt()}>
<View>
<Text>Try login</Text>
</View>
</TouchableNativeFeedback>
</View>
)
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
}
)
My fetch function
login(email,password, callback) {
const API_HEADERS = {
'Accept' : `application/json`,
'Content-Type' : `application/json`
};
let user = {};
user.email = email;
user.password = password;
fetch(`http://myIp:3000/auth/login`, {
method : 'post',
headers : API_HEADERS,
body : JSON.stringify(user)
})
.then((response) => {
if (response.ok) {
return response.json();
} else {
alert(`Wrong data`)
}
})
.then((responseData) => {
console.log(responseData)
})
}
Also wanna add that I using Genymotion as android emul.Another strange thing is that when I press my button first time - nothing happens,but when i press Reload JS , before the component will be unmount , I see my responceData in a console

You should update state, when promise resolved.
.then((responseData) => {
this.setState({
token: responseData.token
});
})
Use catch method:
.then().catch(e => console.log("error: ", e))
Also, where is your alert function? react-native has Alert module.
Alert.alert('any message text');
article about fetch
and this is article using fetch in react-native

Another strange thing is that when I press my button first time
This is very like related to focus.
Try setting keyboardShouldPersistTaps = true for TouchableNativeFeedback

Related

React Native setState not causing rendering

I'm a complete beginner at react native and now I'm stuck with an update problem. I'm using react-native-paper and typescript.
In my app, I want to press a button and then the text field should change its text.
The problem is somehow at the button, or the called function because in the console log its always "before: true after:true" or "before: false after:false",
but what I expected is "before: true after: false" or vice-versa
I've also got a second Text View which is not shown at all.
Maybe someone can tell me what I am doing wrong?
My index.js
import * as React from 'react';
import { AppRegistry } from 'react-native';
import { Provider as PaperProvider } from 'react-native-paper';
import App from './src/App';
export default function Main() {
return (
<PaperProvider>
<App />
</PaperProvider>
);
}
AppRegistry.registerComponent('main', () => Main);
My MyNavigation.tsx (which contains currently my whole app).
import * as React from 'react';
import { BottomNavigation, Text, Avatar, Button, Card, Title, Paragraph, Banner } from 'react-native-paper';
import { View, Image, WebView } from 'react-native';
export default class MyNavi extends React.Component {
constructor(props, context) {
super(props, context);
this.setUnConnected = this.setUnConnected.bind(this);
}
state = {
index: 0,
routes: [
{ key: 'viewcamera', title: 'View', icon: 'remove-red-eye' },
{ key: 'viewsettings', title: 'Settings', icon: 'settings' },
{ key: 'viewhelp', title: 'How-To', icon: 'help' },
],
visible: true,
connected: false,
};
_handleIndexChange = index => { this.setState({ index }); }
setUnConnected = function () {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected });
console.log("after: " + this.state.connected);
console.log("--------------");
};
ViewRoute = () =>
<View style={{ flex: 1, marginTop: 40 }}>
{/* --------- This text field does not get updated -------------*/}
<Text>connected: {this.state.connected ? 'true' : 'false'}</Text>
{/* --------- This text field is not shown at all ------------*/}
<Text>
{this.state.connected}
</Text>
<Button icon="camera" mode="contained" onPress={this.setUnConnected}>
Press me
</Button>
<View style={{ height: 400, width: 400 }}>
<WebView
source={{ uri: 'https://stackoverflow.com/' }}
style={{ marginTop: 40 }}
// onLoad={() => this.setState({ connected: true })}
/>
</View>
</View>
SettingsRoute = () => <Text>Settings</Text>;
HelpRoute = () => <View></View>
_renderScene = BottomNavigation.SceneMap({
viewcamera: this.ViewRoute,
viewsettings: this.SettingsRoute,
viewhelp: this.HelpRoute,
});
render() {
return (
<BottomNavigation
navigationState={this.state}
onIndexChange={this._handleIndexChange}
renderScene={this._renderScene}
/>
);
}
}
State Updates May Be Asynchronous React Documentation
So You cannot test your console.log in this way. Use the callback function of setState method as follows,
this.setState({ connected: !this.state.connected }, () => {
console.log("after: " + this.state.connected);
console.log("--------------");
});
Hope this will help you.
Your issue is here,
setUnConnected = function () {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected });
console.log("after: " + this.state.connected);
console.log("--------------");
};
setState is async function and it takes some time to update the state. It does not block execution of next statements. So you will always get the previous state only for both the console.log.
To get the actual updated value, you should use callback in setState.
setUnConnected = function () {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected }, () => console.log("after: " + this.state.connected); ); //Now you will get updated value.
console.log("--------------");
};
For this,
{/* --------- This text field is not shown at all ------------*/}
<Text>
{this.state.connected}
</Text>
this.state.connected is either true or false (Boolean) which will never be shown on screen. If you still want to see the value on screen, then you can use this hack.
<Text>
{this.state.connected.toString()}
</Text>
Update
From the docs,
Pages are lazily rendered, which means that a page will be rendered the first time you navigate to it. After initial render, all the pages stay rendered to preserve their state.
Instead of this,
_renderScene = BottomNavigation.SceneMap({
viewcamera: this.ViewRoute,
viewsettings: this.SettingsRoute,
viewhelp: this.HelpRoute,
});
You should use this version of renderScene,
_renderScene = ({ route, jumpTo }) => {
switch (route.key) {
case 'viewcamera':
return <ViewRoute jumpTo={jumpTo} connected={this.state.connected} setUnConnected={this.setUnConnected}/>; //Here you can pass data from state and function to your component
case 'viewsettings':
return <SettingsRoute jumpTo={jumpTo} />;
case 'viewhelp':
return <HelpRoute jumpTo={jumpTo} />;
}
}
Your complete code should look like this,
import * as React from 'react';
import { BottomNavigation, Text, Avatar, Button, Card, Title, Paragraph, Banner } from 'react-native-paper';
import { View, Image, WebView } from 'react-native';
const ViewRoute = (props) =>
<View style={{ flex: 1, marginTop: 40 }}>
{/* --------- This text field does not get updated -------------*/}
<Text>connected: {props.connected ? 'true' : 'false'}</Text>
{/* --------- This text field is not shown at all ------------*/}
<Text>
{props.connected.toString()}
</Text>
<Button icon="camera" mode="contained" onPress={props.setUnConnected}>
Press me
</Button>
<View style={{ height: 400, width: 400 }}>
<WebView
source={{ uri: 'https://stackoverflow.com/' }}
style={{ marginTop: 40 }}
// onLoad={() => this.setState({ connected: true })}
/>
</View>
</View>
const SettingsRoute = () => <Text>Settings</Text>;
const HelpRoute = () => <View></View>
export default class MyNavi extends React.Component {
constructor(props, context) {
super(props, context);
this.setUnConnected = this.setUnConnected.bind(this);
}
state = {
index: 0,
routes: [
{ key: 'viewcamera', title: 'View', icon: 'remove-red-eye' },
{ key: 'viewsettings', title: 'Settings', icon: 'settings' },
{ key: 'viewhelp', title: 'How-To', icon: 'help' },
],
visible: true,
connected: false,
};
_handleIndexChange = index => { this.setState({ index }); }
setUnConnected = function() {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected });
console.log("after: " + this.state.connected);
console.log("--------------");
};
_renderScene = ({ route, jumpTo }) => {
switch (route.key) {
case 'viewcamera':
return <ViewRoute jumpTo={jumpTo} connected={this.state.connected} setUnConnected={this.setUnConnected}/>; //Here you can pass data from state and function to your component
case 'viewsettings':
return <SettingsRoute jumpTo={jumpTo} />;
case 'viewhelp':
return <HelpRoute jumpTo={jumpTo} />;
}
}
render() {
return (
<BottomNavigation
navigationState={this.state}
onIndexChange={this._handleIndexChange}
renderScene={this._renderScene}
/>
);
}
}

React Native: Maximum update depth exceeded error

Am getting Maximum update depth exceeded error while entering values into the text field . This is my component .
If i remove the onChangeText event it wont throw error, so the issue is with the event .
These are the dependencies am currently using
react : 16.8.6,
react-dom :16.8.6,
react-native :https://github.com/expo/react-native/archive/sdk-33.0.0.tar.gz,
import { Form, View, Text, Button, Item, Input, Label, Toast, Spinner } from "native-base";
import React from "react";
import { validatePin } from "../services/validate";
export default class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = { userid: "", loading: "false" };
}
async login() {
if (this.state.userid.length == 0) {
Toast.show({
text: "Wrong UserID!",
buttonText: "Okay",
textStyle: { color: "yellow" }
});
} else {
if(this.state.loading === "false" ){
this.setState({ loading: "true" });
}
try {
let res = await validatePin(this.state.userid);
if (typeof res != "undefined") {
const { navigate } = this.props.navigation;
this.setState({ loading: "false" });
if (res.bypassMFA) {
navigate("Password", {
user_id: this.state.userid,
member_name: res.member_name
});
} else {
navigate("MFAComponent", {
userid: this.state.userid,
mfa_id: res.mfa_id,
mfa_q: res.mfa_q,
isValidUser: res.isValidUser
});
}
}
} catch (err) {
console.log("error in login:" + err);
}
}
}
render() {
const { navigate } = this.props.navigation;
return (
<View>
<Form style={{ padding: 5, alignItems: "center" }}>
<Item floatingLabel>
<Label>Online ID</Label>
<Input
value={this.state.userid}
onChangeText={username => this.setState({ userid: username })}
/>
</Item>
{this.state.loading == "true" ? (
<View>
<Spinner size="large" color="#c137a2" />
</View>
) : null}
<Button primary onPress={this.login} style={{ alignSelf: "center", margin: 20 }}>
<Text> Next </Text>
</Button>
</Form>
</View>
);
}
}
First of all you didn't bind login function for onPress. Your onPress props should be like this :
onPress={() => this.login}
or in constructor bind method :
this.login.bind(this)

Google.logInAsync function for OAuth not doing anything in React Native

Google.logInAsync function which is supposed to used for OAuth in React Native, is not giving any response, or is stuck. Take a look at my code, from line 14 to 36. This signIn function is calling Google.logInAsync and is stuck there. No error, nothing. How ever when I press the button again, and the signIn function gets called again, an error is passed: Error: Cannot start a new task while another task is currently in progress: Get Auth
This is my code currently. It shows a label " Sign In With Google" and a Button on the screen. Tapping on the button starts this.signIn funtion, prints "Started logInAsync" on the debugger screen, but theGoogle.logInAsync never completes, no errors are thrown anywhere. console.log('Completed logInAsync') never gets executed!
import {Google} from "expo"
...
console.log('Starting logInAsync')
const result = await Google.logInAsync({
androidClientId:
"<CLIENT_ID>",
scopes: ["profile", "email"]
})
console.log('Completed logInAsync')
that gave an error saying install "expo-google-app-auth"
I installed that module and then changed the import and the function
import * as Google from "expo-google-app-auth"
...
console.log('Starting logInAsync')
const result = await Google.logInAsync({
...
})
console.log('Completed logInAsync')
And after there,
import { StyleSheet, Text, View, Image, Button } from "react-native"
import * as Google from "expo-google-app-auth"
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
signedIn: false,
name: "",
photoUrl: ""
}
}
signIn = async () => {
try {
console.log('Starting logInAsync')
const result = await Google.logInAsync({
androidClientId:
"860657237026-jfosg5hu52u1vedclccs1vgihghva534.apps.googleusercontent.com",
scopes: ["profile", "email"]
})
console.log('Completed logInAsync')
if (result.type === "success") {
this.setState({
signedIn: true,
name: result.user.name,
photoUrl: result.user.photoUrl
})
} else {
console.log("Cancelled!")
}
} catch (e) {
console.log("Error: ", e)
}
}
render() {
return (
<View style={styles.container}>
{this.state.signedIn ? (
<LoggedInPage name={this.state.name} photoUrl={this.state.photoUrl} />
) : (
<LoginPage signIn={this.signIn} />
)}
</View>
)
}
}
const LoginPage = props => {
return (
<View>
<Text style={styles.header}>Sign In With Google</Text>
<Button title="Sign in with Google" onPress={() => props.signIn()} />
</View>
)
}
const LoggedInPage = props => {
return (
<View style={styles.container}>
<Text style={styles.header}>Welcome:{props.name}</Text>
<Image style={styles.image} source={{ uri: props.photoUrl }} />
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
},
header: {
fontSize: 25
},
image: {
marginTop: 15,
width: 150,
height: 150,
borderColor: "rgba(0,0,0,0.2)",
borderWidth: 3,
borderRadius: 150
}
})
No error occur in this code, except if you press the SignIn button twice. I am on the latest version of Node, React-Native and Expo as of now.

Navigation from one page to another in react native using navigate

I am currently learning react native. I want to navigate from one screen to another using navigate() function. Navigate function is placed inside a fetch() function, so after i get the response from the server, I should be redirected to the next page. But currently i an having problem as the navigation code is not working if i put inside the fetch(). If i remove the code and place it outside the fetch then it is working.
Below is my written code (StepOne):
import React, { Component } from 'react';
import { Text, View, StyleSheet, ImageBackground, TextInput, TouchableOpacity, Alert } from 'react-native';
import { Tile } from 'react-native-elements';
export default class StepOne extends Component {
static navigationOptions = {
header: null
};
constructor(props) {
super(props);
this.state = {
userEmail : '',
userPhone : ''
}
}
moveToStepTwo = () => {
const {userEmail} = this.state;
const {userPhone} = this.state;
if(userEmail == '' || userPhone == '') {
Alert.alert('Warning' , 'Please Insert All the Required Details !!!!');
} else {
let url = 'registerProcessGetEmailValidity.jsp?';
let param_one = 'user_email='+userEmail;
let seperator_param = '&';
let full_url = '';
full_url = url + param_one;
let header = {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
fetch( full_url, {
method: 'GET',
headers: header,
})
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
if(myJson.message == 'No User') {
this.props.navigation.navigate('StepTwo', { userEmail: this.state.userEmail , userPhone: this.state.userPhone } );
} else if (myJson.message == 'Got User') {
Alert.alert('Warning' , 'Email is registered, Choose different email !!!!');
}
});
}
}
render() {
const { navigate } = this.props.navigation;
return (
<View style={styles.container}>
<ImageBackground source={require('../../img/background1.jpg')} style={styles.backgroundImage}>
<View style={styles.content}>
<Text style={styles.logo}> -- STEP 1 -- </Text>
<View style={styles.inputContainer}>
<TextInput
underlineColorAndroid='transparent' style={styles.input} placeholder='ENTER EMAIL'
onChangeText = {userEmail => this.setState({userEmail})} >
</TextInput>
<TextInput
underlineColorAndroid='transparent' keyboardType = {'numeric'} maxLength={12}
style={styles.input} placeholder='ENTER PHONE NUMBER' onChangeText = {userPhone => this.setState({userPhone})} >
</TextInput>
</View>
<View style={styles.buttonHolder}>
<TouchableOpacity style={styles.buttonContainer} onPress={ this.moveToStepTwo }>
<Text style={styles.buttonText}>NEXT</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.buttonContainer} onPress={ ()=> navigate('Home') } >
<Text style={styles.buttonText}>CANCEL</Text>
</TouchableOpacity>
</View>
</View>
</ImageBackground>
</View>
);
}
}
When I navigate to the 'StepTwo' screen after the fetch call, there is no response. I can't navigate to the next screen. It is like the navigate call inside the fetch is not working. Can anyone help me to solve this problem ?
And one more thing. Is the any fault in my code ? Since i am new to react native, i have no idea what I am writing is correct or not. Maybe regarding the this. something element.
I give another example (App.js):
import React from 'react';
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
import { StackNavigator } from 'react-navigation';
import Expo from 'expo';
import HomeScreen from './app/screens/HomeScreen';
import LoginScreen from './app/screens/LoginScreen';
import RegisterScreen from './app/screens/RegisterScreen';
const NavigationApp = StackNavigator({
Home: { screen: HomeScreen },
Login: { screen: LoginScreen },
Register: { screen: RegisterScreen },
});
export default class App extends React.Component {
render() {
return (
<NavigationApp />
);
}
}
Then Login file (Login.js)
import React, { Component } from 'react';
import { Text, View, StyleSheet, ImageBackground, TextInput, TouchableOpacity, Alert } from 'react-native';
import { FormLabel, FormInput } from 'react-native-elements'
export default class Login extends Component {
static navigationOptions = {
header: null
};
constructor(props) {
super(props);
this.state = {
userName : '',
userPass : ''
}
}
login = () => {
const {userName} = this.state;
const {userPass} = this.state;
if(userName == '' || userPass == '') {
Alert.alert('Warning' , 'Please Insert All the Required Details !!!!');
} else {
let url = 'loginProcessGetUserDetails.jsp?';
let param_one = 'user_name='+userName;
let param_two = 'user_pass='+userPass;
let param_three = 'user_group=JOMLOKA';
let seperator_param = '&';
let full_url = '';
full_url = url + param_one + seperator_param + param_two + seperator_param + param_three;
let header = {
'Accept': 'application/json',
'Content-Type': 'application/json'
};
fetch( full_url, {
method: 'GET',
headers: header,
})
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
if(myJson.message == 'No User') {
Alert.alert('Warning' , 'No User !!!!');
} else if (myJson.message == 'Wrong Password') {
Alert.alert('Warning' , 'Wrong Password !!!!');
} else if (myJson.message == 'Login') {
//Alert.alert('Success' , 'Login !!!!');
const { navigate } = this.props.navigation;
navigate("Register",{ userEmail: this.state.userEmail , userPhone: this.state.userPhone });
}
});
}
}
render() {
const { navigate } = this.props.navigation;
return (
<View style={styles.container}>
<ImageBackground source={require('../img/background1.jpg')} style={styles.backgroundImage}>
<View style={styles.content}>
<Text style={styles.logo}> -- LOGIN DEMO -- </Text>
<View style={styles.inputContainer}>
<TextInput underlineColorAndroid='transparent' style={styles.input} placeholder='ENTER USERNAME' onChangeText = {userName => this.setState({userName})} >
</TextInput>
<TextInput underlineColorAndroid='transparent' secureTextEntry={true} style={styles.input} placeholder='ENTER PASSWORD' onChangeText = {userPass => this.setState({userPass})} >
</TextInput>
</View>
<View style={styles.buttonHolder}>
<TouchableOpacity onPress={this.login} style={styles.buttonContainer}>
<Text style={styles.buttonText}>LOGIN</Text>
</TouchableOpacity>
<TouchableOpacity onPress={ ()=> navigate('Home') } style={styles.buttonContainer}>
<Text style={styles.buttonText}>CANCEL</Text>
</TouchableOpacity>
</View>
</View>
</ImageBackground>
</View>
);
}
}
That is another example with the same problem.
Replace
onPress={ this.moveToStepTwo }
with
onPress={()=> this.moveToStepTwo() }
this.props.navigation.navigate('StepTwo', { userEmail: this.state.userEmail , userPhone: this.state.userPhone } );
Are you sure your this is correct?
If you don't use Arrow Function, you need to do this var _this = this, then _this.props.navigation.navigate('StepTwo', { userEmail: this.state.userEmail , userPhone: this.state.userPhone } );
You can use Arrow functiom in fetch:
fetch(url, options)
.then(response => {
// ...
})
.catch(error => {
// ...
})
I assume that you have installed react-navigation plugin.
Now import your files in the app.js
like below :
import login from './login';
import StepTwo from './StepTwo';
const Nav = StackNavigator({
login : {screen: login},
StepTwo : {screen: StepTwo},
});
export default class FApp extends Component {
render() {
return (
<Nav/>
);
}
}
And in your login file. (Considering your this file as a login file)
if(myJson.message == 'No User') {
const { navigate } = this.props.navigation;
navigate("StepTwo",{ userEmail: this.state.userEmail , userPhone: this.state.userPhone });
} else if (myJson.message == 'Got User') {
Alert.alert('Warning' , 'Email is registered, Choose different email !!!!');
}

Autocomplete with react native

can someone please help me debug this code, I'd like to use 'react-native-autocomplete-input' library to autocomplete a search text input, basically the api request return a list of stock symbols and company names and I'm thinking to store this file locally to make it faster, for right now I just want to make it work using fetch. The autocomplete must search in responseJson.symbol
Here's what I've done so far:
import Autocomplete from 'react-native-autocomplete-input';
import React, { Component } from 'react';
import { StyleSheet, Text, TouchableOpacity, Platform,
ScrollView, View, ActivityIndicator,
} from 'react-native';
class AutocompleteExample extends Component {
constructor(props) {
super(props);
this.state = {
symbols: [],
query: '',
};
}
searchSymbol(query) {
if (query === '') {
return [];
}
const { symbols } = this.state;
const url = `https://api.iextrading.com/1.0/ref-data/symbols`;
fetch(url)
.then((response) => response.json())
.then((responseJson) => {
this.setState({
symbols:responseJson.symbol,
});
})
.catch((error) => {
console.error(error);
});
return symbols;
}
render() {
if (this.state.isLoading) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
const { query } = this.state;
const symbols = this.searchSymbol(query);
return (
<ScrollView>
<View style={styles.MainContainer}>
<Text style={{fontSize: 12,marginBottom:10}}> Type your favorite stock</Text>
<Autocomplete
autoCapitalize={true}
containerStyle={styles.autocompleteContainer}
data={symbols}
defaultValue={query}
onChangeText={text => this.setState({ query: text })}
placeholder="Enter symbol"
renderItem={({ symbol }) => (
<TouchableOpacity onPress={() => this.setState({ query: symbol })}>
<Text style={styles.itemText}>
{symbol}
</Text>
</TouchableOpacity>
)}
/>
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
MainContainer :{
justifyContent: 'center',
flex:1,
paddingTop: (Platform.OS === 'ios') ? 20 : 20,
padding: 5,
},
autocompleteContainer: {
borderWidth: 1,
zIndex:999,
borderColor: '#87ceeb',
},
itemText: {
fontSize: 17,
color:'#000000',
}
});
module.exports = AutocompleteExample
I don't see any error on the console and the api request is working correctly, I just can't access symbols const Do I have to render something like cloneWithRows(responseJson.symbols) ? Thanks
First of all, the searchSymbol method should be either binded in the component constructor, or declared as a class property. Otherwise, "this" will not point to the component instance.
Then, it seems that your state does not have an isLoading property, but you use it in your render function.
What you should probably do is call your asynchronous searchSymbol method in the componentDidMount lifecycle method. When the promise of the fetch resolves, you should put the result in the state as well as put the isLoading boolean to false. Then your component will re-render with the now available data.

Resources