ReactJS - Can't access properties in object from a fetch - reactjs

I'm doing a fetch to an API and it's returning the data fine, but when I try access the properties, it returns:
Error: Objects are not valid as a React child (found: object with keys {breeds, categories, id, url, width, height}). If you meant to render a collection of children, use an array instead.
myFetch.jsx
import React, {Component} from "react"
class myFetch extends Component {
state={
data:[]
}
componentDidMount(){
const url = "xxxxxxxxxxxxxxxxxxxxxxx"
fetch(url)
.then(r=>r.json())
.then(data=>{
this.setState({data:data})
// console.log(data)
})
.catch(e=>console.log(e))
}
render(){
const {data} = this.state
console.log(data[0])
return (<p>{data[0]}</p>)
}
}
export default myFetch
EDIT
"data" in the state is initialized to an array. Therefore, I should have iterated through the array during the render as
{data.map(d => d.url)} and access whichever property I desire as shown below:
render(){
const {data} = this.state
console.log(data)
return (<p>{data.map(d=>d.url)}</p>)
}

Your data on the state doesn't have any element on 0 index. That's why you getting that undefined error. You can check if it exists before trying to render it.
Something like that:
render() {
const { data } = this.state;
if (data[0]) {
console.log(data[0]);
return <p>{data[0].url}</p>;
}
return null;
}

Related

I can't get my API data to render with the map function React.js

The API does not render and it says this.state.weather.map is not a function. I need help, I have tried different ways of mapping through but does not change the outcome. Any feedback?
import React from 'react';
import axios from 'axios';
export default class Weather extends React.Component {
constructor(props) {
super(props)
this.state = {
weather: []
}
}
async componentDidMount(){
axios.get("http://api.weatherapi.com/v1/current.json?key=?=nyc").then(res => {
console.log(res);
this.setState ({weather: res.data})
});
}
renderWeather(){
this.state.weather.map(weather => {
return (
<p>{weather.location.name}</p>
)
})
}
render () {
return (
<div>
{this.renderWeather()}
</div>
)
}
}
renderWeather(){
return this.state.weather.map(weather => {
return(
<p>{weather.location.name}</p>
)
})
}
you have missed the return statement inside the renderWeather function, above snippet works for you
The API returns JSON.
this.setState ({weather: res.data})
Check the typeof res.data. Is is probably not an array but an object, hence this.state.weather.map is not a function, because Array.map() is an array method.
Make sure that you set the state properly to an array and it should work just fine..

Accessing a property that has an object as it's value

I'm working with an API that retrieves data from a request from a user. The data returned looks something like
{
name: "mewtwo"
id: 150
types: Array(1)
0:
slot: 1
type: {
name: "psychic", url: "https://pokeapi.co/api/v2/type/14"
}
}
I want to access the "psychic" value in type but I receive an undefined for "type". I'm able to display the name property and id just fine I just can't get the "psychic" value.
const Display = (props) => {
console.log(props.data.types);
return (
<>
<h1 className="pokemon-name">{props.data.name}</h1>
<p>{props.data.id}</p>
<p>{props.data.types.type.name}</p>//Will throw an error here
</>
);
}
You are trying to access an array element. Change it to the following
props.data.types[0].type.name
const Display = (props) => {
let { data } = props;
return (
<>
<h1 className="pokemon-name">{data.name}</h1>
<p>{data.id}</p>
<p>{data.types[0].type.name}</p>
</>
);
}
Since data.types is an Array of Objects, I think you want to access the first entry in the data.types array.
So I'd replace {props.data.types.type.name} with {props.data.types[0].type.name}.
For safety reasons I'd check for the existence of that array and extract the data out of it before using it like so:
const Display = ({data}) => {
// destructure properties out of data prop
const { name, id, types } = data;
// extract type name
const typeName = types[0].name;
return (
<>
<h1 className="pokemon-name">{name}</h1>
<p>{id}</p>
<p>{typeName}</p>
</>
);
}
Taking in account that your data is coming from an API, the types prop may not be populated when you're trying to access it. We can account for that scenario like so:
Here's a wrapper component example. this component gets the async data and renders the Display component.
// import as you would to make React work.
class Wrapper extends Component {
constructor(props) {
super(props);
this.state = {
dataFromApi: false
}
}
componentDidMount() {
// async function to get data from api (ex: fetch)
fetchDataFromAPI().then(res => res.json()).then(data => {
this.setState({ dataFromApi: data })
});
}
render() {
const { dataFromApi } = this.state;
return dataFromApi ?
(<Display data={dataFromApi}>) :
(<span>loading</span>);
}
}
Hopefully that makes sense 😅.
Cheers🍻!

Not able to render data from state into the React Component

I am trying to call Firebase and fetch the data in the form of JSON and then pass the objects of the JSON array to some other react-native component.
I am able to successfully make a call to Firebase and then fetch the array of JSON objects. Then later I am storing that list of JSON objects inside the STATE variable and then later trying to pass the object one by one to another react-native component.
Code:
class DisplayCardInformation extends Component
{
const itemsRef = db.ref('firebaseLink/');
state = {
listDataFromDB: null,
lastKey: null
};
componentDidMount()
{
itemsRef.on('value', ( snapshot ) => {
var data = snapshot.val();
this.setState({
listDataFromDB : snapshot.val(),
});
var keys = Object.keys(data);
keys.forEach((key) =>
{
this.setState({
lastKey: key
});
});
});
}
render() {
this.state.listDataFromDB.map((listDataItem) => {
return(
<CardInformation listItem = {listDataItem} />
);
});
}
}
export default DisplayCardInformation;
I am trying to pass the information to the CardInformation.
Error:
TypeError: TypeError: null is not an object (evaluating 'this.state.listDataFromDB.map')
This error is located at:
in DisplayCardInformation (at Home.js:33)
You need to set the initial value of the state to any array like:
state = {
listDataFromDB: []
};
What is happing in your case is that, when your component renders for the first time, it passes null to the child component.

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

Meteor - how to give tracker autorun a callback

I have a little piece of code that renders data from the database according to the path name. My only problem is that when I try to retrieve that data, using this.state.note._id it returns an error that says it cannot find _id of undefined. How would I access my object that is put into a state? It only gives the error when I try to access the items inside the object such as _id
import React from "react";
import { Tracker } from "meteor/tracker";
import { Notes } from "../methods/methods";
export default class fullSize extends React.Component{
constructor(props){
super(props);
this.state = {
note: [],
document: (<div></div>)
};
}
componentWillMount() {
this.tracker = Tracker.autorun(() => {
Meteor.subscribe('notes');
let note = Notes.find({_id: this.props.match.params.noteId}).fetch()
this.setState({ note: note[0] });
});
}
renderDocument(){
console.log(this.state.note);
return <p>Hi</p>
}
componentWillUnmount() {
this.tracker.stop();
}
render(){
return <div>{this.renderDocument()}</div>
}
}
I know that the reason it is returning undefined is because (correct me if I am wrong) the page is rendering the function before the the tracker could refresh the data. How would I get like some sort of callback when the tracker receives some data it will call the renderDocument function?
You're initializing your note state as an array but then you're setting it to a scalar later. You're also not checking to see if the subscription is ready which means that you end up trying to get the state when it is still empty. The tracker will run anytime a reactive data source inside it changes. This means you don't need a callback, you just add any code you want to run inside the tracker itself.
You also don't need a state variable for the document contents itself, your render function can just return a <div /> until the subscription becomes ready.
Note also that .findOne() is equivalent to .find().fetch()[0] - it returns a single document.
When you're searching on _id you can shorthand your query to .findOne(id) instead of .findOne({_id: id})
import React from "react";
import { Tracker } from "meteor/tracker";
import { Notes } from "../methods/methods";
export default class fullSize extends React.Component{
constructor(props){
super(props);
this.state = {
note: null
};
}
componentWillMount() {
const sub = Meteor.subscribe('notes');
this.tracker = Tracker.autorun(() => {
if (sub.ready) this.setState({ note: Notes.findOne(this.props.match.params.noteId) });
});
}
renderDocument(){
return this.state.note ? <p>Hi</p> : <div />;
}
componentWillUnmount() {
this.tracker.stop();
}
render(){
return <div>{this.renderDocument()}</div>
}
}

Resources