I tried to call some rest API, but I got some issue.
Uncaught TypeError: Cannot read property 'name' of undefined
So here's my code:
class MovieDetailViewer extends React.Component {
constructor() {
super();
this.state = {
movieResponse: {}, // wont work
movieResponse: {
"id": 1,
"barcode": "000-000-003",
"name": "Django Unchained",
"fsk": "FSK_18",
"lend": false,
"deleted": false,
"genre": {
"id": 1,
"name": "Western"
},
"source": {
"id": 1,
"name": "Blu-Ray"
}
} // will work
}
componentDidMount() {
fetch('apiurltogetmovie')
.then((response) = > response.json())
.then(responseJson = > {
this.setState({ movieJsonResponse: responseJson.movie });
});
}
render() {
return (
<div>
<MovieDetails
movie={this.state.movieJsonResponse}
/>
</div>
);
}
}
class MovieDetails extends React.Component {
render() {
const movie = this.props.movie;
return (
<div>
<h1>{movie.name}</h1>
<h2>{movie.gerne.name} - {movie.source.name}</h2>
<p>
{movie.comment}
</p>
</div>
)
}
}
It looked like there was a problem with JSON response but if initialize movieResponse with some default JSON it works.
At google I cant find any react example where the state is initialize with some value. Whats the best practice here?
So I am not sure why gerne.name is undefinied. If I log the JSON response to console it will be fine.
The problem is that before the API call in MovieDetailViewer finishes, MovieDetails is rendered. But, since the response is not available, movieResponse is still equal the initial value, which in this case is an empty object. Thus, when attempting to access movie.genre.name, since movie is empty, genre will be undefined and name unable to be accessed. To fix this, you should only render MovieDetails once the response is available:
class MovieDetailViewer extends React.Component {
constructor() {
super();
this.state = {
movieResponse: null
}
}
...
render() {
return (this.state.movieJsonResponse) ? (
<div>
<MovieDetails movie={this.state.movieJsonResponse} />
</div>
) : (
<div className="loader">loading...</div>
);
}
}
It is worth noting that your check should likely be more thorough than this, and involve another property on the state describing the current status of the query (querying, success, error, etc.)
Best practices that can be used here:
Use prop-types to force props to be the same type you expect for.
Validate object inner fields, for example: movie && movie.name.
Use conditional render, for example { movie && movie.name && <h1>{movie.name}</h1>}
Related
I'm trying to render a nested JSON in react but I receive the error "TypeError: Cannot convert undefined or null to object" when I try to render the nested parts. Those parts are converted to objects when are stored in the state of the component by the componentDidMount() method, so I'm trying to use Object.keys() function.
For expample, in the next JSON:
{
"gear": 14,
"valid": 1,
"meteo": {
"wind_direction": 152,
"wind_velocity": 10.1
},
}
When I try to render it using the Object.keys() function, like this:
const haul = this.state.haul
{Object.keys(haul.meteo).map( key => {
return(<p>Sea state: {haul.meteo[key]} </p>)
})}
the error is thrown in the Object.keys() line, and I don't understand why.
The complete component is this:
class ComponentsHaul extends Component {
constructor(props) {
super(props);
this.state = {
haul: []
};
this.apiHaul = "http://myapi";
}
componentDidMount() {
fetch(this.apiHaul)
.then(response => {
return response.json();
})
.then(haul => {
this.setState(() => {
return {
haul
};
});
});
}
render() {
const haul = this.state.haul
return (
<Fragment>
<p>Gear: {haul.gear}</p>
{Object.keys(haul.meteo).map( key => {
return(<p>Sea state: {haul.meteo[key]} </p>)
})}
</Fragment>
);
}
}
haul is initially an array, there is no meteo, so Object.keys(haul.meteo) fails. You then later change the type (a no-no) to an object, keep the type consistent.
const state = { haul: [] };
console.log(Object.keys(state.haul.meteo));
If you change your initial state to provide an empty meteo object this should work for you on initial and subsequent renders while data is fetched.
this.state = {
haul: {
meteo: {},
},
}
const state = { haul: { meteo: {} } };
console.log(Object.keys(state.haul.meteo));
This is because when you first render the component your state is empty.
Just add a condition:
{ hulu.meteo.length > 0 && Object.keys(haul.meteo).map( key => {
return(<p>Sea state: {haul.meteo[key]} </p>)
})}
You should first check if your haul state has meteo. When the component rendered your fetch data will not be ready yet. So, it is not safe to use property that doesn't exist yet.
render() {
const haul = this.state.haul;
if(!haul.meteo) {
return <p>Loading...</p>
}
return (
<Fragment>
<p>Gear: {haul.gear}</p>
{Object.keys(haul.meteo).map((key) => {
return <p key={key}>Sea state: {haul.meteo[key]} </p>;
})}
</Fragment>
);
}
And, of course, don't forget to add key prop while rendering array of React children.
This issue is coming on first render.
See, haul is state containing empty array []. When it renders first time, React tries to access meteo at line
Object.keys(haul.meteo)
But haul has no property named meteo which is undefined. Thus, React reads it like
Object.keys(undefined)
And you are getting this error. (componentDidMount runs after 1st render)
Solution: Instead check existence of meteo first, like this.
{
haul.meteo ? Object.keys(haul.meteo).map( key => {
return(<p>Sea state: {haul.meteo[key]} </p>)
})} : []
}
I am trying to fetch data from an API (the Magic the Gathring Scryfall API) that has a nested object while using ReactJS. As soon as I try to use data from a nested object, I get a "cannot read png of undefined". I figured out this was probably an async problem, and fixed it by changing the state of my initial array to null, then adding an if statement to the render, but as soon as I changed the API url from https://api.scryfall.com/cards?page=3 to https://api.scryfall.com/cards/search?order=cmc&q=c%3Ared+pow%3D3, I can no longer access the image urls in the nested object again, despite having a JSON in the same format returned to me as the first URL. I'm just at a loss now.
I tried using axios, and I tried putting the fetch into a separate function, then putting that function into componentDidMount, but no luck. Setting 'cards' to null and then putting the "if (cards === null) { return null; } into the render worked for the first link, but not the second.
import React,{Component} from 'react';
import logo from './logo.svg';
import './App.css';
import Login from './components/Login'
class App extends Component {
constructor() {
super()
this.state = {
cards: null,
isUpdated:false
}
}
componentDidMount() {
this.populateCards()
}
populateCards() {
let url = 'https://api.scryfall.com/cards/search?order=cmc&q=c%3Ared+pow%3D3 '
fetch(url)
.then(response => response.json())
.then(json => {
console.log("setting the state.")
console.log(json.data)
this.setState({cards: json.data})
})
}
render() {
const { cards } = this.state;
if (cards === null) {
return null;
}
let cards1 = this.state.cards
let cardItems = cards1.map((card) => {
return (
<li>{card.name} - {card.id}
<p></p><img src={card.image_uris.png}/></li>
)
})
return (
<div className="App">
<h1>HOME PAGE</h1>
<Login />
<ul>
{cardItems}
</ul>
</div>
)
}
}
export default App;
Just need to figure out what is going on with this JSON before I can move on to writing up some search boxes. Greatly appreciate any help that can be offered.
The JSON coming back looks like so:
{
"object": "list",
"total_cards": 248583,
"has_more": true,
"next_page": "https://api.scryfall.com/cards?page=4",
"data": [
{
"object": "card",
"id": "18794d78-5aae-42de-a45b-3289624689f1",
"oracle_id": "a6543f71-0326-4e1f-b58f-9ce325d5d036",
"multiverse_ids": [
463813
],
"name": "Gateway Plaza",
"printed_name": "門前廣場",
"lang": "zht",
"released_at": "2019-05-03",
"uri": "https://api.scryfall.com/cards/18794d78-5aae-42de-a45b-3289624689f1",
"scryfall_uri": "https://scryfall.com/card/war/246/zht/%E9%96%80%E5%89%8D%E5%BB%A3%E5%A0%B4?utm_source=api",
"layout": "normal",
"highres_image": false,
"image_uris": {
"small": "https://img.scryfall.com/cards/small/front/1/8/18794d78-5aae-42de-a45b-3289624689f1.jpg?1556241680",
"normal": "https://img.scryfall.com/cards/normal/front/1/8/18794d78-5aae-42de-a45b-3289624689f1.jpg?1556241680",
"large": "https://img.scryfall.com/cards/large/front/1/8/18794d78-5aae-42de-a45b-3289624689f1.jpg?1556241680",
"png": "https://img.scryfall.com/cards/png/front/1/8/18794d78-5aae-42de-a45b-3289624689f1.png?1556241680",
"art_crop": "https://img.scryfall.com/cards/art_crop/front/1/8/18794d78-5aae-42de-a45b-3289624689f1.jpg?1556241680",
"border_crop": "https://img.scryfall.com/cards/border_crop/front/1/8/18794d78-5aae-42de-a45b-3289624689f1.jpg?1556241680"
},
"mana_cost": "",
"cmc": 0,
Some object on your response does not have the image_uris property so it throw error.
Add these line
let filtered = cards1.filter(card => card.image_uris);
And then map over filtered array, you will get what you need
let cardItems = filtered.map(card => {
return (
<li>
<img src={card.image_uris.png}/>
</li>
);
});
I'm running into a recurring issue in my code where I want to grab multiple pieces of data from a component to set as states, and push those into an array which is having its own state updated. The way I am doing it currently isn't working and I think it's because I do not understand the order of the way things happen in js and react.
Here's an example of something I'm doing that doesn't work: jsfiddle here or code below.
import React, {Component} from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
categoryTitle: null,
categorySubtitle: null,
categoryArray: [],
}
}
pushToCategoryArray = () => {
this.state.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
setCategoryStates = (categoryTitle, categorySubtitle) => {
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
})
this.pushToCategoryArray();
}
render() {
return (
<CategoryComponent
setCategoryStates={this.setCategoryStates}
categoryTitle={'Category Title Text'}
categorySubtitle={'Category Subtitle Text'}
/>
);
}
}
class CategoryComponent extends Component {
render() {
var categoryTitle = this.props.categoryTitle;
var categorySubtitle = this.props.categorySubtitle;
return (
<div onClick={() => (this.props.setCategoryStates(
categoryTitle,
categorySubtitle,
))}
>
<h1>{categoryTitle}</h1>
<h2>{categorySubtitle}</h2>
</div>
);
}
}
I can see in the console that I am grabbing the categoryTitle and categorySubtitle that I want, but they get pushed as null into this.state.categoryArray. Is this a scenario where I need to be using promises? Taking another approach?
This occurs because setState is asynchronous (https://reactjs.org/docs/state-and-lifecycle.html#using-state-correctly).
Here's the problem
//State has categoryTitle as null and categorySubtitle as null.
this.state = {
categoryTitle: null,
categorySubtitle: null,
categoryArray: [],
}
//This gets the correct values in the parameters
setCategoryStates = (categoryTitle, categorySubtitle) => {
//This is correct, you're setting state BUT this is not sync
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
})
this.pushToCategoryArray();
}
//This method is using the state, which as can be seen from the constructor is null and hence you're pushing null into your array.
pushToCategoryArray = () => {
this.state.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
Solution to your problem: pass callback to setState
setCategoryStates = (categoryTitle, categorySubtitle) => {
//This is correct, you're setting state BUT this is not sync
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
}, () => {
/*
Add state to the array
This callback will be called once the async state update has succeeded
So accessing state in this variable will be correct.
*/
this.pushToCategoryArray()
})
}
and change
pushToCategoryArray = () => {
//You don't need state, you can simply make these regular JavaScript variables
this.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
I think React doesn't re-render because of the pushToCategoryArray that directly change state. Need to assign new array in this.setState function.
// this.state.categoryArray.push({...})
const prevCategoryArray = this.state.categoryArray
this.setState({
categoryArray: [ newObject, ...prevCategoryArray],
)}
So I quite new to this. I want to load a external json file into react, and then use it.
The problem apparently is that the json hasn't been yet loaded, so I get errors. Like Cannot read property 'map' of undefined etc. The console.log give:
1
3
Uncaught TypeError: Cannot read property 'map' of undefined
So I've read this has to do with asynchronous things. But I can't find any example on how to fix it. Or how to make it work.
I would really appreciate it to see a small example like this, to make it work.
Later on I want to make it possible to filter the json with <input type=text etc> with some kind of dynamic search. But first things first. So can someone help me out?
This is my simple file:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(){
super();
this.state = {
data: []
};
console.log('1');
};
componentDidMount() {
fetch("http://asite.com/file.json")
.then( (response) => {
return response.json() })
.then( (json) => {
this.setState({data: json});
console.log('2');
})
};
render() {
console.log("3");
return(
<div className="Employee">
{
this.state.data.employees.map(function(employee) {
return (
<div>
<h1> {employee.name} </h1>
</div>
)
})
}
</div>
)
}
}
export default App;
Since you have this.state.data.employees, I would assume you want to shape the initial state like this:
constructor(){
super();
this.state = {
data: {
employees: []
}
};
};
You can either choose to save to the state another variable which is flipped when you know you've loaded the data, or just check to ensure the data exists before trying to map it.
The latter could be done like below:
<div className="Employee">
{ this.state.data.employees &&
this.state.data.employees.map(function(employee) {
return (
<div>
<h1> {employee.name} </h1>
</div>
)
})
}
</div>
Or you could adjust the initial state to include an empty (but initialized) version of the return such as:
this.state = {
data: { employees: [] }
};
Checking to ensure the state contains the data field you're mapping is much safer though incase the return ever doesn't include the field.
I'm new to React and I'm trying to build (for now) a simple app that allows you to search for a name in a list of transactions and return the transaction details to you. So far I have been struggling to deal with the API request and I keep running into errors. I'm using superagent for my API calls
import React, {Component } from 'react';
import Request from 'superagent'
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {};
}
componentWillMount() {
var url = 'https://api.myjson.com/bins/2dorw';
Request.get(url).then((response) => {
this.setState({
items: response.body.items,
});
});
}
render() {
var names = this.state.items.map((name, index) => {
<div>
<li key={index}> {this.state.items.merchant.name } </li>
</div>
});
return (
<div className="App">
{names}
</div>
);
}
}
export default App;
I can't even make this simple API call to work, just display the names from the API endpoint. This has been going on for a day or so, and usually I get the object either null, undefined and type errors.
Uncaught (in promise) TypeError: Cannot read property '_currentElement' of null(…)
App.js:64 Uncaught TypeError: Cannot read property 'map' of undefined
I have tried following tutorials but I haven't been able to find one that would work in my case, so I can't wrap my head around the API call.
UPDATE
After changing to componentDidMount(), adding isFetching and setting items to an array in setState (as the two answers have suggested), I am now getting an
Uncaught (in promise) TypeError: Cannot read property 'name' of undefined
Using componentWillMount() is slightly early in the lifecycle. Wait until it has mounted using componentDidMount() That should help resolve the first error being thrown.
The second error comes from this.state.items.map when the items have not shown up yet. I would also suggest adding a isFetching state, to check if the data has shown up yet.:
class App extends Component {
constructor() {
super();
this.state = {
isFetching: true
};
}
componentDidMount() {
var url = 'https://api.myjson.com/bins/2dorw';
Request.get(url).then((response) => {
this.setState({
items: response.body.items,
isFetching: false
});
});
}
render() {
if (this.state.isFetching) return null;
var names = this.state.items.map((item, index) => {
<div>
<li key={index}> {item.merchant.name} </li>
</div >
});
return (
<div className="App">
{names}
</div>
);
}
}
export default App;
Update
Added a slight change - looks like your map function was also a bit off. It's hard to say what exactly you are looking for, because I don't know the data structure exactly.
You had
var names = this.state.items.map((name, index) => {
<div>
<li key={index}> {this.state.items.merchant.name } </li>
</div>
});
Passing name as the first param, which is the entire item. You then call this.state all over again - which you shouldn't need to do. I think this is where some of the undefined issues are coming from as well.
this.state.items is undefined because you declared state = {}, so can not map over it. You can fix this problem by making items an empty array.
constructor() {
super();
this.state = {
items: []
};
}