Cannot access state inside fetch in React Native - reactjs

In React Native, I have the following:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, Text, View, TouchableHighlight } from 'react-native';
import Immutable from 'immutable';
const styles = StyleSheet.create({
map: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: '20%',
},
});
export default class VirtualFenceBottom extends Component {
constructor(props) {
super(props);
this.state = { markers: this.props.markers };
}
populateMarkers = () => {
let markersVar = this.state.markers;
fetch('http://localhost:8080/virtualFence', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}}).then(function(response){
console.log('GET markers success');
// Parse it as JSON
parsedResponse = JSON.parse(response["_bodyInit"]);
console.log('response after JSON.parse: ',parsedResponse);
if (parsedResponse.length > 0) {
//update state here
console.log('parsedResponse in if statement: ',parsedResponse);
// this.setState({markers: parsedResponse});
} else {
console.log('There were no markers in db');
}
console.log('markersVar: ',markersVar);
// console.log('this.state.markers after setState: ',this.state.markers);
}).catch(function(error) {
console.log('GET markers error');
console.log("GET markers error: ",error);
});
};
render() {
return (
<View>
<TouchableHighlight onPress={this.populateMarkers}>
<Text style={styles.text}>Populate markers from DB</Text>
</TouchableHighlight>
</View>
);
}}
All goes as expected, except I cannot access state directly inside fetch. Strangely, I can access it inside the containing function with markerVar. It seems there is a particular issue with fetch itself.
My goal is to update the state with the response. None of the existing answers for similar questions seem to work in my case. What do I do?
UPDATE 1: Added the code for the entire component.
UPDATE 2: Fixed a misspelling of the parsedResponse variable that was causing one part of the error.

The callbacks that are called in response to fetch results are just unbuond functions.
Try this:
}}).then(function(response){
console.log('this', this); // see that 'this' is not
// what you expect it to be
Note, that function() { ...} in javascript creates closure that captures all local variables (including your marketsVar, but not _this__.
So, 'this' points into 'window' variable which doesn't have state (usually).
To fix this issue, you can
1) use fat arrow functions then and in catch handlers:(we're in 2018!, your toolchain handles it for sure):
fetch(...)
.then(() => {
this.setState(...) // this is defined. magic!
2) create alias to this - that is used sometimes:
var that = this;
fetch(...)
.then(function() {
that.setState(...); // that is defined in same way as marketsVar
})
3) bind your handlers manually but that's ugly as hell so i don't recommend it.

Be sure that this function exists inside of the component, otherwise it will not have access to the lexically scoped this.state method.
class MyComp extends React.Component {
state = { val: 0 }
myMethod = () => {
this.setState({ val: 2 })
}
}
Also consider whenever you pluck values from state, keeping the names consistent. For example:
const { markers } = this.state

Related

How to fix an status 429 error even when calling an API once?

I'm trying to use this API to obtain information about space shuttles. I used axios to get information about the space crafts, but I get a Status 429 error. I researched, and found out that a status 429 error means that you called the API too many times, but I only called in once in my code... Could you please give me advice for solving this problem?
Code:
import React from 'react'
import { View, Text, Alert, FlatList, StyleSheet } from 'react-native'
import axios from 'axios'
export default class SpaceCraftScreen extends React.Component {
constructor() {
super()
this.state = {
spaceCraftData: {}
}
}
getSpaceCraftData = async () => {
axios
.get("https://ll.thespacedevs.com/2.2.0/config/spacecraft/")
.then(response => {
this.setState({spaceCraftData:response.data.results})
console.log(this.state.spaceCraftData)
})
.catch(error => {
Alert.alert(error.message)
})
}
componentDidMount = () => {
this.getSpaceCraftData()
//setInterval(() => {this.getSpaceCraftData()}, 10000)
}
render() {
if(Object.keys(this.state.spaceCraftData).length === 0) {
return(
<View>
<Text style = {{textAlign:'center', marginTop:50, fontSize:50, fontWeight: 'bold'}}>Loading data</Text>
</View>
)
}
else {
let craftArray = Object.keys(this.state.spaceCraftData).map(craftData => {
return this.state.spaceCraftData[craftData]
})
let spaceCrafts = [].concat.apply([], craftArray)
spaceCrafts.sort(function(a,b) {
a.agency.founding_year - b.agency.founding_year
})
console.log("SpaceCrafts: "+spaceCrafts)
spaceCrafts.slice(0,10)
return(
<View>
</View>
)
}
}
}
can you try to alter your componentDidMount function a bit:
componentDidMount = () => {
if (!Object.keys(this.state.spaceCraftData).length) {
this.getSpaceCraftData();
}
}
The reason your axios call is repeating infinitely, is because you update your state inside the getSpaceCraftData function, so you trigger a re-render of your component. When the component mounts again it executes the getSpaceCraftData function again and the state is updated again, and so on.
By adding a condition that executes the function only if there is no data, then you should put an end to the execution. However this is not a complete solution, since you would need to store a state for errors as well and the condition should test if there is no error as well. If an error is encountered then you will still get an infinite loop, because there will be no data and the fetching process will probably always fail.

Rendering loop error with react axios setState

I have different ingredients(vodka, gin, whiskey...) json files in a dummy folder.
I have an IngredientList.js where I select one ingredient and pass it down to
IngredientSearch.js
The IngredientSearch.js gets the relevant json file based on the ingredient name and then I set the state of ingredientRes to the res.data.drinks
Problem I am getting is that when I print the console.log(newVals) --> the console logs the arrays from the json infinitely. Seems like I am rerendering something infinitely.
What is wrong with my setup?
IngredientSearch.js:
import React, { Component } from 'react';
import axios from 'axios';
class IngredientSearch extends Component {
constructor() {
super();
this.state = {
ingredientRes: []
};
}
componentDidUpdate(prevProps) {
let ingredient = this.props.ingredient; //for example: vodka
this.getIngredient_drinks(ingredient);
}
getIngredient_drinks = (ingredient) => {
if(ingredient !== null) {
axios.get(`../dummy/${ingredient}.json`)
.then((res)=>{
let newVals = [];
newVals.push(res.data.drinks);
//console.log(newVals); // keeps relogging the arrays
this.setState({ ingredientRes: newVals });
}).catch((err)=>{
console.log(err);
})
}
}
render() {
return (
<div>
IngredientSearch Results
I want to map the ingredientRes here
</div>
)
}
}
export default IngredientSearch;
You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like -
if (this.props.ingredient !== prevProps.ingredient) {
this.getIngredient_drinks(ingredient);
}
Otherwise it will cause an infinite loop.
For reference - https://reactjs.org/docs/react-component.html#componentdidupdate

Why is my react state data undefined in react?

I am creating a react 360 application using an API to fetch data and then display it on a panel. Below I have the following code:
export class CryptoControlInformation extends React.Component {
state = {
details: {
description: '',
links: [],
}
};
componentDidMount() {
const CRYPTO_CONTROL_PATH = 'https://cryptocontrol.io/api/v1/public/details/coin/bitcoin?key=some_key';
fetch(CRYPTO_CONTROL_PATH)
.then(response => response.json())
.then(data => {this.setState({
details: {
description: data["description"],
links: [...this.state.details.links, ...data["links"] ]
}})
})
}
render() {
let links = this.state.details.links;
##################################
console.log(links[0])
{_id: "5b41d21fa8a741cf4187ec60", type: "reddit", name: "r/Bitcoin Reddit", link: "https://reddit.com/r/Bitcoin/"}
####################################
####################################
// Why is this returning undefined?
console.log(links[0]["name"]);
####################################
return (
<View>
<View style={styles.rightHeader}>
<Text style={{fontSize: 40}}>Information</Text>
</View>
<View>
<Text>{this.state.details.description}</Text>
</View>
<View>
<Text>{this.state.details.description}</Text>
</View>
</View>
)
}
}
I can't get the information inside my object and I don't understand why. I know that the information is there. I can console.log the object in its entirety but the individual pieces are undefined. What am I doing wrong? I've noticed in react that the state always has to be explicitly detailed.
For example, I found that I can't just do this:
export class Square extends React.Component {
state = {
info: undefined
};
componentDidMount() {
// grab information (pseudocode)
this.setState{info: data}
}
}
I have to actually map out the data which is annoying:
export class Square extends React.Component {
state = {
info: {
color: '',
height: '',
width: '',
}
};
componentDidMount() {
// grab information (pseudocode)
this.setState{info: {
color: data['red'],
heigth: data['height'],
width: data['width']
}
}
}
}
I'm thinking that this has something to do with my problem. Am I on the right track?
Standard timing problem - you didn't look for 'react undefined', right?
When component loads data render is called (minimum) twice - once at initial mounting (w/o data) and 2nd time when data arrives (setState forces new render)
console.log (in chrome) cheats you silently updating earlier message
you can use map - it works fine with initially empty array - or check if value is ready in jsx
{!links.length && <Text>{links[0]["name"]}</Text/>}
... conditionally call rendering function, return <Loading /> earlier, etc.
Using setState with function (Varun's comment) isn't required, it's safer (for some cases) but not obligatory

When mapStateToProps in create create a new axios call

I have created a Reactjs component that receives a mapStateToProps function call. Everything works fine except the ajax call using Axios.
The class on a mapStateToProps update needs to call the server and add its payload to the state of the component and update the textarea.
The error I am getting from the console is,
ReactDOMIDOperations.js:47 Uncaught RangeError: Maximum call stack size exceeded
Below is what I have so far. Can anyone show me how to fix this issue?
import React from "react";
import { connect } from "react-redux";
import ApiCalls from "../../../utils/ApiCalls";
const mapStateToProps = state => {
return { passFilePath: state.passFilePath };
};
/**
* This component is a template to display
* widgets of information
*/
class IdeTextEditorClass extends React.Component {
constructor() {
super();
this.state = {
newData: [],
pathData: []
}
}
/**
* Received request from server add it to
* react component so that it can be rendered
*/
componentDidUpdate() {
try {
this.setState({ pathData: this.props.passFilePath[this.props.passFilePath.length - 1] });
} catch (err) {
this.setState({ pathData: '' });
}
console.log('path', this.state.pathData.data);
ApiCalls.readSassFile(this.state.pathData.data)
.then(function (serverData) {
this.setState({ newData: serverData[0].data })
}.bind(this));
}
render() {
try {
this.state.newData
} catch (err) {
this.setState({ newData: '' });
}
return (
<fieldset>
<input type="text" value={this.state.pathData.data} />
<textarea id="ide-text-area" name="ide-text-area" value={this.state.newData} /></fieldset>
)
}
}
const IdeTextEditor = connect(mapStateToProps)(IdeTextEditorClass);
export default IdeTextEditor;
class IdeTextEditorClass extends React.Component {
constructor() {
super();
/*
based on your original code it seems the default data should be empty string ,as you set them to be empty string when you cannot get data from server.
*/
this.state = {
newData: '',
pathData: ''
}
}
/**
* Received request from server add it to
* react component so that it can be rendered
*/
componentDidMount() {
try {
this.setState({ pathData: this.props.passFilePath[this.props.passFilePath.length - 1] });
} catch (err) {
this.setState({ pathData: '' });
}
console.log('path', this.state.pathData.data);
ApiCalls.readSassFile(this.state.pathData.data)
.then(function (serverData) {
this.setState({ newData: serverData[0].data })
}.bind(this));
}
render() {
//by default your newData is already empty string. so skip the checking here.
let path = this.state.pathData ? this.state.pathData.data : '';
return (
<fieldset>
<input type="text" value={path} />
<textarea id="ide-text-area" name="ide-text-area" value={this.state.newData} /></fieldset>
)
}
}
Explanation:
The major change is to change componentDidUpdate to componentDidMount.
Putting the data initializing logic in componentDidMount because:
called only once, thus avoiding the endless update loop mentioned in the comments. Also, initialization logic is usually expected here.
this method is called after initial render, so you can at least display something to user during the wait for data (from server). for example, in your render method, you can check newData and if it is not available, display a loading icon. Then React calls componentDidMount, and fetch your data -> update state -> trigger render again -> displays your input / text area using new data fetched from server. Of course, if you don't want to bother showing a loading icon, it is also fine, because your view will probably be updated quickly, when the ajax call returns.

Cannot read property 'propertyName' of undefined

I'm working on a project in react-native where I have troubles of accessing an element inside an object array by passing it as a prop where I want it to be used. Requirement is to get the name property out and set it to a text inside a flatlist.
The structure of my object array as follow.
[
{
"media1":[
{"name":"Lynn"},
{"name":"Michelle"},
{"name":"Carter"}
]
},
{
"media2":[
{"price":"23"},
{"price":"76"},
{"price":"39"}
]
}
]
This is how is pass this object array as a prop where I want it to be used
return (
<View>
<AlbumDetail data = {this.state.allData}/>
</View>
);
This is where I want it to be used
const AlbumDetail = (props) => {
return (
<View>
{console.log(props.data[0])} //Working
{console.log(props.data[0].media1[0].name)} //Not working
// Requirement as bellow
<Text>{wants to set the "name" here}</Text>
<Text>{wants to set the "price" here}</Text>
</View>
);
};
How can I achieve this ??
You might want to place two missing comma's.
One after:
{"name":"Michelle"}
And one after
{"price":"76"}
AlbumDetail has no way to know it has a property called data. You need to write AlbumDetail function as a React.Component class.
You are passing a JSON object into AlbumDetail, you need to call JSON.parse(data) before use it. UPDATE: .then(resp => resp.json()) is used to parse json.
Place console.log before return. The object you returned should be pure JSX components.
The code below should solve your problem:
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
const url =
'http://purelight-prod.appspot.com/api/user/v2/browse/homescreendata';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: undefined,
};
}
componentDidMount() {
fetch(url)
.then(resp => resp.json())
.then(respJson => {
this.setState({
data: respJson,
});
})
.catch(err => {
console.error(err);
});
}
render() {
return (
<View style={{ flex: 1 }}>
<TestView data={this.state.data} />
</View>
);
}
}
class TestView extends React.Component {
render() {
!!this.props.data && console.log(console.log(data[0].healer[0].healerid));
return (
<View>
<Text>Hello World!</Text>
</View>
);
}
}
Edit:
Use componentDidMount(), because we like to display something (loading icon, etc), and then update the View when data arrived.
This is an async task. The data has to be held until it arrived. I use !!this.props.data && ..., so it only displays when it is not undefined.
Since the API response is a relatively big package, it will be much easier to work with, if you use TypeScript and create an object class to parse it.
I don't think the API helper package provides correct response in your code.

Resources