Cannot read property 'propertyName' of undefined - reactjs

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.

Related

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

Binding data from a promise to a react component is inaccessible

I have two classes, one being a child component called article. I want to pass data to the article component after receiving data in the parent, which is my app.js. However, I am unable to drill down into the data from the response that I am setting in the App constructor--the data I am trying to access is undefined, but I am able to print the full response out fine, including the data that I am unable to drill down into. I would like to tie the articles array from the following JSON to my respective article components.
Data is being pulled from: https://newsapi.org/v2/top-headlines?sources=google-news&apiKey=edd0276dc8344c2abaeb40a3f6fb439f
class Article extends Component {
constructor(props) {
super(props);
this.props.title = props.title;
this.props.description = props.description;
this.props.url = props.url;
}
render() {
let pic = {
uri: 'https://cdn.vox-cdn.com/thumbor/M8Nb8mKymSEN59T2iDIe5XXiNTw=/0x146:2040x1214/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/11738447/VRG_ILLO_2761_Spacecraft_Sidebyside.jpg'
};
return (
<View style={styles.article}>
<Header/>
<Text>Two rockets launched within 15 minutes of each other Wednesday morning</Text>
<Image source={pic} style={{ height: 250}}/>
<Text numberOfLines={3}>Early in the morning on July 24th, rocket enthusiasts will have the lucky experience of being able to watch two launches at roughly the same time. Around 7:30AM ET, SpaceX is slated to launch one of its Falcon 9 rockets from the California coast, while Europe…</Text>
</View>
);
}
}
export default class App extends Component {
constructor(props) {
super(props);
let url = 'https://newsapi.org/v2/top-headlines?sources=google-news&apiKey=edd0276dc8344c2abaeb40a3f6fb439f';
this.state = fetch(url)
.then(function(response) {
response.text().then(function(text) {
console.log(text, "TEXT!!!");
return text;
});
});
}
render() {
return (
<ScrollView>
<Article/>
</ScrollView>
);
}
}
You shouldn't really be doing your fetch in the constructor.
Here's a working example to get you started:
import React from "react";
import ReactDOM from "react-dom";
const Article = ({ title, desc, url }) => {
return (
<React.Fragment>
<h1>{title}</h1>
<p>{desc}</p>
<img src={url} />
</React.Fragment>
);
};
let url =
"https://newsapi.org/v2/top-headlines?sources=google-news&apiKey=edd0276dc8344c2abaeb40a3f6fb439f";
class App extends React.Component {
state = {
articles: []
};
componentDidMount() {
fetch(url)
.then(res => res.json())
.then(data => {
this.setState({
articles: data.articles
});
});
}
render() {
if (this.state.articles.length === 0) {
return "Loading..";
}
const firstArticle = this.state.articles[0];
const { title, description, url } = firstArticle;
return <Article title={title} desc={description} url={url} />;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Working CodeSandbox here.
A couple things wrong about this:
the return type of a Promise<T> is a Promise<T> unless you await, in which case it's T.
you don't assign to this.state, state changes in React are abstracted away via this.setState
Following,
this.state = fetch(url)...
should be changed to
fetch(url)
.then(response => response.text())
.then(text => this.setState({ text }))
pass in this.state.text (or rename it) as props to the child
edit: as Colin pointed out in the comments below, assigning to this.state is fine in the constructor (which you are doing). However, fetching data is better suited for post-mount lifecycle methods like componentDidMount

Handling responded forms in react

I'm doing a simple project that has something like 3 forms and right now I start the component with empty Inputs and then request data from API to pre-populate the form using the componentWillMount() hook.
It works for me now but if someday my app need more and more data it would be annoying to do this everytime for any new form and I would like to know if there is any lib or pattern to help pre-populating forms without using any state container (Redux, mobx, and I really don't know if they are needed in this case).
It is better to do your data fetching in componentDidMount than in componentWillMount:
If you need to load data from a remote endpoint, this is a good place
to instantiate the network request.
If you want to reuse some data fetching logic without any external state you could use Component with render props or Higher Order Components.
For example:
function withData(fetchData) {
return BaseComponent => {
class WithData extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount(){
fetchData().then(response => {
this.setState({ data: response })
})
}
render(){
return <BaseComponent {...this.props} data={this.state.data} />
}
}
return WithData;
}
}
And later you can reuse this logic:
const DataList = ({ data }) => (
<ul>
{
data.map(item =>
<li>{item.name}</li>
)
}
</ul>
)
// passing Promises as a `data` producers
const UserDataList = withData(fetchUsers)(DataList);
const GroupDataList = withData(fetchGroups)(DataList);
const CatsDataList = withData(() => fetchAnimals('cats'))(DataList);
const ListOfEverything = () => (
<Container>
<UserDataList />
<GroupDataList />
<CatsDataList />
</Container>
)

React component does not react to mobx observable data

I am using mobX for my react native project. Please consider this store class:
class Birds {
#observable listOne = [];
#observable fetchingListOne = false;
#observable fetchErrorOne = '';
#action setListOne = () => {
this.fetchingListOne = true;
api.getList()
.then((data) => {
this.listOne.replace(data);
this.fetchingListOne = false;
})
.catch((error) => {
this.fetchingListOne = false;
this.fetchErrorOne = error;
});
};
}
And this the react component:
#inject('BirdStore') #observer
export default class Flat extends React.Component {
componentDidMount() {
this.props.BirdStore.setListOne();
}
_renderHeader = () => {
return <Text style={styles.listHeaderText}>
Hello {this.props.BirdStore.listOne.length} is {this.props.BirdStore.fetchingListOne.toString()}
</Text>;
};
_renderItem = ({item}) => {
return <Text style={styles.item}>{item.name}</Text>
};
_renderFooter = () => {
if (this.props.BirdStore.fetchingListOne) {
return <ActivityIndicator/>
}
else {
return null
}
};
render() {
const dataSource = this.props.BirdStore.listOne.slice();
return (
<View style={styles.container}>
<Text>Fetching: {this.props.BirdStore.fetchingListOne.toString()}</Text>
<FlatList
style={styles.listContainer}
ListHeaderComponent={this._renderHeader}
data={dataSource}
renderItem={this._renderItem}
keyExtractor={(item, i) => item.id}
ListFooterComponent={this._renderFooter}
/>
</View>
)
}
}
From above it looks to me that:
When the Flat component mounts, it call the method of the store setListOne().
setListOne() sets fetchingListOne to true and makes an api call.
On the component side, when the fetchingListOne is true, the ActivityIndicator displays, and in the ListHeaderComponent it should display true.
On the store side, after successful/unsuccessful response, it sets fetchingListOne to false.
Finally on the component side, because fetchingListOne is set to false, ActivityIndicator should not display and in the ListHeaderComponent it should display false.
However, this is not what's happening. Here when the setListOne() method is called, after it sets the fetchingListOne to true, the component does not react to the changes made after api call. And the ActivityIndicator keeps displaying and in ListHeaderComponent its displaying true.
What am I doing wrong here? Could you please help me. Thank you
Update
I have added a Text component before the FlatList. Adding a Text component or console logging inside the component class's render method does makes the FlatList react to the changes. I don't know why this is happening though.
The problem you are running into here most probably, is that although Flat is an observer component, FlatList is not (it's an built-in component after all). In this setup _renderFooter and the others are part are rendered by render of FlatList, but not of FlatList. Hence they are not part of the lifecycle of Flat, but of FlatList and as such are not tracked by Mobx
There are two ways to fix this, both pretty simple:
1) declare _renderItem as observer component:
_renderItem = observer(({item}) =>
<Text style={styles.item}>{item.name}</Text>
);
2) use an inline anonymous Observer component:
_renderItem = ({item}) =>
<Observer>{
() => <Text style={styles.item}>{item.name}</Text>}
</Observer>

How to access this.setstate in onShouldStartLoadWithRequest() of a WebView in React Native?

I'm really struggling to understand how to read and set this.state inside of functions called by WebView when doing specific operations. My end goal is to:
Show a activity indicator when the user clicks a link inside the webview
Perform certain actions based on the URL the user is clicking on
I'm very new to React, but from what I've learned, I should use () => function to bind this from the main object to be accessible inside the function.
This works on onLoad={(e) => this._loading('onLoad Complete')} and I can update the state when the page loaded the first time.
If I use onShouldStartLoadWithRequest={this._onShouldStartLoadWithRequest} I can see that it works and my console.warn() is shown on screen. this.state is of course not available.
However if I change it to onShouldStartLoadWithRequest={() => this._onShouldStartLoadWithRequest} the function doesn't seem to be executed at all, and neither this.state (commented in the code below) or console.warn() is run.
Any help is appreciated!
import React, { Component} from 'react';
import {Text,View,WebView} from 'react-native';
class Question extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
debug: 'Debug header'
};
}
render() {
return (
<View style={{flex:1, marginTop:20}}>
<Text style={{backgroundColor: '#f9f', padding: 5}}>{this.state.debug}</Text>
<WebView
source={{uri: 'http://stackoverflow.com/'}}
renderLoading={this._renderLoading}
startInLoadingState={true}
javaScriptEnabled={true}
onShouldStartLoadWithRequest={this._onShouldStartLoadWithRequest}
onNavigationStateChange = {this._onShouldStartLoadWithRequest}
onLoad={(e) => this._loading('onLoad Complete')}
/>
</View>
);
}
_loading(text) {
this.setState({debug: text});
}
_renderLoading() {
return (
<Text style={{backgroundColor: '#ff0', padding: 5}}>_renderLoading</Text>
)
}
_onShouldStartLoadWithRequest(e) {
// If I call this with this._onShouldStartLoadWithRequest in the WebView props, I get "this.setState is not a function"
// But if I call it with () => this._onShouldStartLoadWithRequest it's not executed at all,
// and console.warn() isn't run
//this.setState({debug: e.url});
console.warn(e.url);
return true;
}
}
module.exports = Question;
To access correct this (class context) inside _onShouldStartLoadWithRequest, you need to bind it with class context, after binding whenever this method will get called this keyword inside it will point to react class.
Like this:
onShouldStartLoadWithRequest={this._onShouldStartLoadWithRequest.bind(this)}
or use arrow function like this:
onShouldStartLoadWithRequest={this._onShouldStartLoadWithRequest}
_onShouldStartLoadWithRequest = (e) => {...}
Or like this:
onShouldStartLoadWithRequest={(e) => this._onShouldStartLoadWithRequest(e)}
Check this answer for more details: Why is JavaScript bind() necessary?

Resources