Can not access properties in JavaScript object array (React.js) - javascript

I have a React.js component which pulls its initial state data from an API call in componentDidMount(). The data is an array of objects.
I am able to view the array, and individual elements using JSON.stringify (for debugging), but when I try to access a property in an element, I get an error which seems to imply that the element is undefined, despite having checked that it is not.
Code:
class TubeStatus extends Component {
constructor(props) {
super(props);
this.state = { 'tubedata' : [] };
};
componentWillMount() {
let component = this;
axios.get('https://api.tfl.gov.uk/line/mode/tube/status')
.then( function(response) {
component.setState({ 'tubedata' : response.data });
})
.catch( function(error) {
console.log(JSON.stringify(error, null, 2));
});
};
render() {
return (
<div><pre>{this.state.tubedata[0].id}</pre></div>
);
}
}
Error:
Uncaught TypeError: Cannot read property 'id' of undefined
If I use JSON.stringify() to display this.state.tubedata, all the data is there.
In my admittedly limited knowledge of React.js, I suspect this is because React.js is trying to access the .id property before componentDidMount() fires loading the initial state data, thus returning undefined, but I could be completely wrong.
Anyone able to point me in the right direction?

As you are fetching data from API call, on initial rendering data is not available hence you are getting error.
this.state.tubedata.length>0// check if tubedata is available
So,
render() {
return (
<div><pre>{this.state.tubedata.length>0?this.state.tubedata[0].id:null}</pre></div>
);
}

this is because you are having a async request and since the state array is initially empty this.state.tubedata[0] is initially undefined
Keep a check before using id like
<div><pre>{this.state.tubedata.length > 0 && this.state.tubedata[0].id}</pre></div>
class TubeStatus extends React.Component {
constructor(props) {
super(props);
this.state = { 'tubedata' : [] };
};
componentWillMount() {
let component = this;
};
render() {
return (
<div>Hello <pre>{this.state.tubedata.length > 0 && this.state.tubedata[0].id}</pre></div>
);
}
}
ReactDOM.render(<TubeStatus/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Related

How to prevent render method from throwing error on a state that is initially set to undefined

I am trying to render my state which updates by pulling data from an api so the initial value of the state is undefined. I am receiving the error TypeError: undefined is not an object (evaluating 'this.state.Pods[0].Name'). From what I understand about react, every time the state is updated the components that use that state re-renders, so the text that initially was undefined will then render the updated state value. My question is could you bypass the initial error from being thrown so it can render the value of state when its updated. Here's how im calling the state:
<Text>{this.state.Pods[0].Name}</Text>
Ive tried using getDerivedStateFromError but I it isn't working for me.
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <Text>Something went Wrong</Text>
}
Well, verify it before you do anything.
So verify if this.state.Pods is there and this.state.Pods[0] is there.
Having said that, directly accessing an index and then only using that single one, begs the question, why would you need an array?
So, you can first verify if this.state.hasError is true, then return the error message, if it's not true, and you don't have the pods yet, then return null, otherwise return your actual render, like:
render() {
const { hasError, Pods = [] } = this.state;
if (hasError) {
return <div>Error occured</div>;
}
if (Array.isArray(Pods) || !Pods[0]) {
return null;
}
return <Text>{Pods[0].Name}</Text>;
}
When the state gets updated, your component will rerender and it will again check all the conditions, giving you either an error, again null (if Pods is not there yet or not what you expect it to be) or it will render your text element
Please use this
render() {
if (!this.state.Pods || !this.state.Pods[0] ) {
return <Text>Loading</Text>
}
That is not the correct way of using error boundary component. if you are expected
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
In your component.
render() {
<ErrorBoundary>
<Text>{this.state.Pods[0].Name}</Text>
</ErrorBoundary>
}
though you don't need error boundry in your case.
you should read your state safely as suggested in other answers.
You have to update your code as below.
<Text>{Array.isArray(this.state.Pods) && this.state.Pods.length > 0 && this.state.Pods[0].Name}</Text>
Hope this will work for you!
Try using constructor or instance variable like:
class A extends React.Component {
state = {
Pods: []
}
}
OR
class A extends React.Component {
constructor() {
this.state = {
Pods: []
}
}
}
This way your state will have a value intially, now inside render you can check if your data from the API has arrived:
render(){
if (this.state.Pods.length) {
return <div>{this.state.Pods}</div>
}
return ''
}

Data from API rendering as empty (array/object issue)

I have a components that aims to display data from API:
class Item extends Component {
constructor(props) {
super(props);
this.state = {
output: []
}
}
componentDidMount() {
fetch('http://localhost:3005/products/157963')
.then(response => response.json())
.then(data => this.setState({ output: data }));
}
render() {
console.log(this.state.output);
return (
<ItemPanel>
<ItemBox>
<BoxTitle>{this.state.output}</BoxTitle>
</ItemPanel>
);
}
export default Item;
console.log(this.state.output) returns proper data, while my component won't render, its throwing this error:
Objects are not valid as a React child (found: object with keys {id, general, brand, images}). If you meant to render a collection of children, use an array instead.
I guess the data from api is an object. I have tried using JSON.parse or toString() already :/
This is output from console:
It seems like you are displaying whole object in <BoxTitle>, Let's try to show brand here. I have update the code given in question.
Updated initial state output [] to {}
class Item extends Component {
constructor(props) {
super(props);
this.state = {
output: {}
}
}
componentDidMount() {
fetch('http://localhost:3005/products/157963')
.then(response => response.json())
.then(data => this.setState({ output: data }));
}
render() {
console.log(this.state.output);
const { brand = {name : ""} } = this.state.output // added default name until we get actual value
return (
<ItemPanel>
<ItemBox>
<BoxTitle>{brand.name}</BoxTitle>
</ItemPanel>
);
}
export default Item;
While your component fetches from your API it renders the element tree described in the render function.
Initial state for output is an empty array and that's a good start.
You should consider what to display to your application user when loading data from the API or if the network request fails.
I'm quite sure that you don't want to render the object returned upon a successful API call as-is.
That said, JSON.stringify function can be used for viewing what result is set in state upon a successful API call before picking what fields will be displayed, how and where they are displayed.
you can better use conditional rendering,
render() {
console.log(this.state.output);
return (
<ItemPanel>
<ItemBox>
{
if(this.state.output.brand.name !=== null) ?
<BoxTitle>{this.state.output.brand.name}</BoxTitle> :
<BoxTitle>Brand Name is Empty</BoxTitle>
}
</ItemPanel>
);
}

Reactjs, Cannot read property 'lessons' of null

In my code I tell the API to retrieve the data from the end point inside componentDidMount().
componentDidMount() {
this.setState({
lessons: API.getAllLessons()
})
}
Then I map each item inside the list to an individual panel inside the render
render() {
return (
this.state.lessons.map((lesson)=>{
<LessonItem lesson={lesson}/>
})
);
}
However, it throws an error when mapping as the property in the state it's trying to map is null, but it shouldn't be as data is returned from the API.
Uncaught (in promise) TypeError: Cannot read property 'lessons' of null
My state gets defined like so
export interface AuthenticatedHomeState {
currentUser: any;
lessons: any;
}
you probably didn't initialised the state.
constructor(props){
super(props)
this.state = {
lessons: [] //default value
}
}
componentDidMount() {
this.setState({
lessons: API.getAllLessons()
})
}
However, if API.getAllLessons returns a Promise you will need to handle it differently.
you are calling componentDidMount, meaning it will run after render had been called. You need to call componentWillMount()
You have to intialise the state first, then call the api, and if the api call is a promise call(which usually is) you will have to read the values in such a way,
constructor(props){
super(props)
this.state = {
lessons: [] //set default value for lessons
}
}
componentDidMount() {
API.getAllLessons().then((resp)=>{
this.setState({
lessons: resp.data
})
})
}

React Redux - Error on render because expecting props that didn't arrive yet

After fetching the data from an API, and putting that data on the Redux state, I'm using a helper function in mapStatetoProps to select and modify part of that data and pass it modified to the props.
So without the rendering I can see in the console.log that everything goes as it should.
Empty props: this.props.pageContent = {}
The data fetch and mapped to props: this.props.pageContent = { pageSection: [{something here that I don't want}, {}, {}...] }
The data as I want it selected and passed to the props: this.props.pageContent = { pageSection: [{card: 'my Data'}, {}, {}...] }
but when I pass some propsto a component it throws an error because those props that I'm trying to pass haven't arrived yet to this.props (in this case card.cardTitle)
This is my code so far:
class Page extends Component {
componentWillMount() {
this.props.fetchPageContent();
}
render() {
console.log(this.props.pageContent)
if (!this.props.pageContent.pageSection[0].card) return null;
return (
<div>
<PageSection
introSectionCardTitle={ this.props.pageContent.pageSection[0].card.cardTitle}
introSectionCardContent={ this.props.pageContent.pageSection[0].card.cardContent}
/>
</div>
);
}
Any ideas?
before the return I tried to have an if statement with diferent options, but the error keeps the same:
TypeError: Cannot read property '0' of undefined
You have a problem here if (!this.props.pageContent.pageSection[0].card)
replace
if (!this.props.pageContent.pageSection[0].card)
with
if(this.props.pageContent && this.props.pageContent.pageSection && this.props.pageContent.pageSection[0].card)
Because you are not sure that your props has pageContent and you are also not sure that pageSection exist, because before setting the props pageContent is undefined and you are trying to access an object inside it and then find element inside an array
Try the updated code below:
class Page extends Component {
componentWillMount() {
this.props.fetchPageContent();
}
render() {
console.log(this.props.pageContent)
if(this.props.pageContent && this.props.pageContent.pageSection && this.props.pageContent.pageSection[0].card)
{
return (
<div>
<PageSection
introSectionCardTitle={ this.props.pageContent.pageSection[0].card.cardTitle}
introSectionCardContent={ this.props.pageContent.pageSection[0].card.cardContent}
/>
</div>
);
}
else
{
return (<div></div>);
}
}

Which one is called first? constructor or componentDidMount or Render

As i've read online and official documentation. Order is
Constructor-> ComponentWillMount -> Render -> ComponentDidMount and others.
class Navigation extends Component {
constructor(props) {
super(props);
console.log('constructor', this);
this.state = { items: [ ] };
}
componentDidMount() {
console.log('componentDidMount');
if ( toDisplay( ) ) {
api.bringCats()
.then( categories => {
const tot_cat = categories.map( cat => { return {name: cat.name, link: cat.link}; })
this.setState({items: tot_cat,})
})
}
}
render() {
console.log("render")
//some stuff
}
}
im expecting the the log to be in this order
1. constructor
2. ComponentDidMount
3. render
im logging this with constructor inside constructor method. im able to get value for items which is getting its value in componentDidMount why this is happening? and its logging in the correct order (constructor->didmount->render) but why im getting value of items even before calling componentDidMount.
im using <Navigation/> component inside <Header/>
Header extends Component{
render(){
return (<div> <Navigation/> </div>);
}
}
i was trying something and found out that this is working fine.
window.cnj = require('circular-json') /* i added this in starting point first line so i will have access to this inside all imports. */
window.props = JSON.parse(window.cnj.stringify(simple));
I can access window.props from the devtools and its working as expected.
sample can be string, arrays, normal object or circular object.

Categories