I cannot get into nested JSON object, but only sometimes - javascript

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>
);
});

Related

Handling fetched objects in React components

I'm a React novice, learning on FCC, and I'm tearing my hair out trying to work with a fetched object.
I'm trying to build a basic 'Random Quote Machine' React component which fetches (and then renders values from) an object. The object (fetched in componentDidMount) has a single key (quotes) which is an array of objects (each of which has quote and author keys), and I assign this array to local state (quotesData) and then as a constant in the render.
Essentially, the component should display a random quote on first render and then again each time the New Quote button is clicked. So, I assign (at random) one of the objects in the array to nextQuote.
It's after this that I run into problems. I can console.log(quotesData) (and nextQuote) without issue, but why can I not then assign values from the object stored in nextQuote to nextQuoteQuote or nextQuoteAuthor, using dot (or bracket) notation?
Here's a link to the pen (the full code of which is also below): https://codepen.io/igorgetmeabrain/pen/rNmwMEM
Right now, it just keeps throwing object errors at me and won't render at all. Please help!
class RandomQuotes extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isFetching: false,
quotesData: [],
random: Math.random()
};
this.getNewQuote = this.getNewQuote.bind(this);
this.fetchData = this.fetchData.bind(this);
}
fetchData() {
this.setState({isFetching: true});
fetch ('https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json',
{headers: {
Accept: 'application/json'
}})
.then(res => res.json())
.then((data) =>
{
this.setState({quotesData: data.quotes, isFetching: false});
})
.catch(error => {
console.log(error);
this.setState({isFetching: false, error: error});
});
}
getNewQuote() {
this.setState({
random: Math.random()
});
}
componentDidMount() {
this.fetchData();
}
render() {
const {quotesData, isFetching, error} = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (isFetching) {
return <div>Loading...</div>;
} else {
const randomIndex = Math.floor(this.state.random * quotesData.length);
const nextQuote = quotesData[randomIndex];
const nextQuoteQuote = nextQuote.quote;
const nextQuoteAuthor = nextQuote.author;
return (
<div id="quote-box">
<p id="text">{nextQuoteQuote}</p>
<p id="author">{nextQuoteAuthor}</p>
<button id="new-quote" onClick={this.getNewQuote}>
New Quote
</button>
</div>
);
}
}
}
ReactDOM.render(<RandomQuotes />, document.getElementById("root"));
The fastest fix would be to change isFetching: true in the state.
It happens because in the beginning if isFetching: false and error: false it goes into last } else { part where you define
const nextQuoteQuote = nextQuote.quote;
const nextQuoteAuthor = nextQuote.author;
which are undefined because data is fetching in the background and they are not fetched yet.
So basically when you have to fetch data in the componentDidMount method you set loading state to true from the beginning so component knows it has to wait for it to fetch and later on show the data.

How can I pull a specific object from the state and get one of its items?

I have the following component named Tweets.js:
import React, {Component} from "react";
export default class Tweets extends Component {
constructor(props) {
super(props);
this.state = {tweets: [], users: []};
}
componentDidMount() {
this.TweetList();
this.UserList();
}
TweetList() {
fetch('http://***.***.***.***:****/api/posts')
.then(res => res.json())
.then((data) => {
this.setState({ tweets: data })
})
.catch(console.log);
}
UserList() {
fetch('http://***.***.***.***:****/api/users')
.then(res => res.json())
.then((data) => {
this.setState({ users: data })
})
.catch(console.log);
}
render() {
const tweets = this.state.tweets.map((item, i) => (
<div>
<div class="tweet_box">
<p>{item.tweet}</p>
</div>
</div>
));
const users = this.state.users.map((item, i) => (
<div>
<h2>{item.twitterhandle}</h2>
</div>
))
return (
<div className="tweet">{ tweets }</div>
)
}
}
users json:
[
{
"userid": 337282,
"twitterhandle": "sean",
"email": "sean#sean.com"
},
{
"userid": 1234563,
"twitterhandle": "DidItWork",
"email": "pleasework#work.com"
},
{
"userid": 3728,
"twitterhandle": "Isthisup",
"email": "isthisup#up.com"
},
{
"userid": 1234567,
"twitterhandle": "blah",
"email": "matt#knop.com"
}
]
posts json:
[
{
"postid": 2,
"tweet": "Hello, World #2",
"userid_id": 337282
},
{
"postid": 4,
"tweet": "Hello, World #1",
"userid_id": 3728
},
{
"postid": 1,
"tweet": "Hello, World #4",
"userid_id": 3728
},
{
"postid": 3,
"tweet": " Hello, World! How's it?",
"userid_id": 1234563
}
]
I am trying to return the list of Posts from the posts json file and I was able to accomplish that. My question is, how am I able to take the userid_id number, find it in the users json file, pull the username, and display it inside the following code?
const tweets = this.state.tweets.map((item, i) =>
<div>
<div class="tweet_box">
<p>{item.tweet}</p>
</div>
</div>
));
All I have been able to accomplish is to get it to display a separate list of the users but I can't figure out how to route the data into the above code - in say, an h1 tag.
I am very new to react and I apologize if my code is terrible. I am open to any and all suggestions or reference I can look at. I looked through the React documentation and found the section on keys and it appeared to be the correct direction, but still couldn't figure it out.
If there is anything I can add to clarify my issue, please don't hesitate to ask.
I appreciate all of your time.
EDIT: I am attempting to work through the problem some more and added the following function:
GetUsername() {
return this.state.users[1];
}
I then made the following changes to my render:
const tweets = this.state.tweets.map((item, i) => (
<div>
<div class="tweet_box">
<h1>{this.GetUsername()}</h1>
<p>{item.tweet}</p>
</div>
</div>
));
I added
<h1>{this.GetUsername()}</h1>
Which produced the following error:
Error: Objects are not valid as a React child (found: object with keys
{userid, twitterhandle, email, password}). If you meant to render a
collection of children, use an array instead.
You are close. React doesn't render objects directly but can render primitives, like strings and numbers. this.state.users[1] will always return the second user object in the users array, which probably isn't what you want.
Update your utility to take an id and find the correct user object.
getUserById = (id) => this.state.users.find(({ userid }) => userid === id);
Then call that in the render to get the user object and if found access the twitterhandle property.
const tweets = this.state.tweets.map((item, i) => (
<div>
<div class="tweet_box">
<h1>{this.getUserById(item.userid_id)?.twitterhandle}</h1>
<p>{item.tweet}</p>
</div>
</div>
));
This GetUserName method returns an object (the user's object). It cannot render an object, but it can render a string or a number. for example:
GetUsername() {
return this.state.users[1].twitterhandle;
}
Also you can return a array and iterate with a map like you do in the const "tweet"
The error you are getting, "Objects are not valid as a React child", is because your GetUsername function actually returns the whole user object instead of just the username. You need to change this.state.users[1] to this.state.users[1].twitterhandle.
But we want to find the right user for each tweet, not just return the first user all the time. We do this by looking through the users array and finding the element where user.userid matches tweet.userid_id.
Let's make a getUsername function that takes the numeric user id as an argument:
getUsername( id ) {
const user = this.state.users.find( user => user.id === id );
if ( user ) {
return user.twitterhandle;
} else {
return ''; // fallback to return an empty string if no user was found
}
}
(You can write that a lot more concisely, but I wanted to be clear about the intention)
Now we can call that with the id from the tweet item
const tweets = this.state.tweets.map((item, i) => (
<div>
<div class="tweet_box">
<h1>{this.getUsername(item.userid_id)}</h1>
<p>{item.tweet}</p>
</div>
</div>
));

JavaScript Dropdown Box Issue - Array Map not working

I'm new to JavaScript and I am having a massive problem trying to populate a dropdown box with values that are getting queried from a SQL database and returned as a JSON file. My server code seems to work well querying the DB and if I hit the server directly it gives me the following JSON data:
[{
"key": "1AEFF22E-7A2C-4920-B72A-255119E785A8",
"value": "ExampleSSRSProject"
}, {
"key": "5A8AE6D3-4A96-4048-9207-6DDDA5B7D19E",
"value": "MyReportPackage"
}, {
"key": "EA2CD590-FA01-4094-86EE-414C860E597A",
"value": "CoverSheet"
}]
However, when I run the client page code I just get the error:
"Warning: Each child in a list should have a unique "key" prop".
Below I have listed my code from the server.js, client componentDidMount(), and the render.
app.get('/api/reportList', (req,res) => {
connection.connect(err=>{
if(err){
console.log(err);
res.statusCode = 200;
res.setHeader('content','text/plain');
res.end(err);
}
else{
var sqlrequest = new sql.Request(connection);
sqlrequest.query("select ItemId as 'key',Name as 'value' FROM
ReportServer$SQL2014.dbo.Catalog where Name <> ''",(err,result)=>{
if(err){
console.log(`SQL Error`);
res.statusCode = 200;
res.setHeader('content','text/plain');
res.end("SQL Error");
}
else{
console.log(result);
res.statusCode = 200;
res.setHeader('content','text/plain');
res.json(result.recordset);
connection.close();
}
})
}
})
});
componentDidMount() {
fetch("api/reportList")
.then((response) => {
return response.json();
})
.then(data => {
let reportsFromApi = data.map((report,index) => {
return {key: {index}, display: report}
});
this.setState({
reports: reportsFromApi
});
}).catch(error => {
console.log(error);
});
render() {
return (
<div>
<select>
{this.state.reports.map((report) => <option key={report.ItemId} value={report.Name}
>{report.Name}</option>)}
</select>
</div>
)
}
import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import ReportList from './components/reportList/reportList';
class App extends Component {
render() {
return (
<div className="App">
<ReportList/>
</div>
);
}
}
export default App;
I think that is because in your React component, you use the ItemId attribute as the "key" prop.
{this.state.reports.map((report) => <option key={report.ItemId} value={report.Name}>...</option>)}
The problem is that your "report" objects that are passed from your server code to your client code don't seem to have an "ItemId" property. That means all of your option elements are passed a "key" property that is equal to undefined and therefore, that is not unique.
You could use the "key" attribute of your "report" objects though, like this:
{this.state.reports.map((report) => <option key={report.key} value={report.Name}>...</option>)}

ReactJS - TypeError: Cannot read property 'name' of undefined

I'm fetching data from an API and I want to show on the screen some objects
(for example 'name' object) and I'm getting the following error:
"TypeError: Cannot read property 'name' of undefined"
I already tried to show data after the api request (with booleans)
Does anyone know what might be the problem?
class App extends Component {
constructor(props) {
super(props)
this.state = {
products: {},
}
this.fetchDevices = this.fetchDevices.bind(this);
}
async fetchDevices() {
const url = 'my url';
const response = await fetch(url, {method : 'GET', headers : headers});
const data = await response.json()
this.setState({products : data.value})
}
componentDidMount() {
this.fetchDevices();
}
render() {
return (
<div>
{this.state.products ? <p>{this.state.products[0].name}</p> : null}
</div>
)}
}
export default App
{
"value": [
{
"urn": null,
"name": "New_COMEC",
"description": null,
"icon": "icon-energy-meter",
"customIdFormat": null,
"customIdDisplay": true,
"customIdRequired": true,
"serialNumberFormat": null,
"serialNumberDisplay": true,
"serialNumberRequired": false,
....,
}
]
You have initialized your products state as an empty object instead of an array.
The render method will be called before your fetch call, hence the app breaks.
Since you initialized as an object,
{this.state.products ? <p>{this.state.products[0].name}</p> : null}
is true in the initial render, so it tries to get the first array element when the state is actually an object at that time..
Your code ought to be like
class App extends Component {
constructor(props) {
super(props)
this.state = {
products: [],
};
and
{Array.isArray(this.state.products) && this.state.products[0] ? <p>{this.state.products[0].name}</p> : null}

React - Fill data before api call?

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>}

Categories

Resources