Trouble accessing and displaying JSON using ReactJS and the fetch-api - javascript

everyone thanks for the help!
I have looked at several similar questions but have not been able to extrapolate their answers to solve my problem.
I am using a ReactJS application to consume JSON from a website. I'm using the code from https://pusher.com/tutorials/consume-restful-api-react and changing it to fit my situation.
Currently, when I view index.js, I get the error "TypeError:
assetList.assets is undefined." Given the the JSON and code below, what do I need to change to
display a list of the assets and their properties?
I would like something like the display to look like the Desired Display below.
Desired Display.
There are two 2 assets:<br/>
id: 1317 Filename: PROCESS_FLOW.pdf
id: 1836 Filename: 004527_FS.jpg
JSON consumed from website
{"totalNumberOfAssets":2,
"assets":[
{"id":"1317","attributes":{"Filename":["PROCESS_FLOW.pdf"]}},
{"id":"1836","attributes":{"Filename":["004527_FS.jpg"]}}
]}
components/assetList.js
import React from 'react'
const AssetList = ({assetList}) => {
return (
<div>
There are {assetList.totalNumberOfAssets} assets:
{assetList.assets.map((asset) => (
<div>
id: {asset.id} filename: {asset.filename}
</div>
))}
</div>
)
};
export default AssetList
App.js
import React, {Component} from 'react';
import AssetList from './components/assetList';
class App extends Component {
render() {
return (
<AssetList assetList={this.state.assetList} />
)
}
state = {
assetList: []
};
componentDidMount() {
fetch('http://ligitaddress/api/v1/asset')
.then(res => res.json())
.then((data) => {
this.setState({ assetList: data })
})
.catch(console.log)
}
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));

On your first render, the value of this.state.assetList is an array:
state = {
assetList: []
};
However you are passing it into <AssetList>
const AssetList = ({assetList}) => {
return (
<div>
There are {assetList.totalNumberOfAssets} assets:
{assetList.assets.map((asset) => (
<div>
id: {asset.id} filename: {asset.filename}
</div>
))}
</div>
)
};
The line saying assetList.assets.map is trying to call map() on something that is undefined. (you can access the property assets on an array and it will be undefined) It seems like it expects assetList to be an object with an assets array in it, but in your parent component assetList is initialized to an array... in short you're confusing yourself as to what kind of data you expect to be where.
Either change your initial state to reflect how you expect it to be passed into <AssetList>:
state = {
assetList: {
assets: []
}
};
And/or change your <AssetList> component to properly check its prop:
const AssetList = ({assetList}) => {
return (
<div>
There are {assetList.totalNumberOfAssets} assets:
{Array.isArray(assetList.assets) && assetList.assets.map((asset) => (
<div>
id: {asset.id} filename: {asset.filename}
</div>
))}
</div>
)
};

This is happening because your components/assetList.js is trying to access assetList.assets on assetList.assets.map without it being defined.
When the API request is made and has not returned yet, the assets on assetList have not being defined, since assetList on App.js is initialized to an empty array.
You can replace the line on components/assetList.js with assetList.assets && assetList.assets.map(...) and that should do it

Related

How can I dynamically import in a NextJs page data (array) not entire component

Hi all I followed the next tutorial
https://nextjs.org/docs/advanced-features/dynamic-import
and works great for components
But I need to fetch data dynamically.
Situation:
I have one simple component named MyItems that receives as props items which is a list of elements title, category.
I want to dynamically import these lists from typescript files stored in page-data/myitems/de|en.ts and so on
So these ts files export array after doing some calculations that is why i don't import json dynamically or search for other solution. I need them to have code and export an array
export default [{name: 'somename', title: somemagic()]
I have this page in pages/page
const Page = async ({ t, ...props }: Props) => {
const locale = props._nextI18Next.initialLocale;
const items = (await import(`../page-data/myitems/${locale}`)).default;
console.log(items); // HERE I SEE OUTPUT FINE
return (
<>
<Head>
<title>dynamic test</title>
</Head>
{/*#ts-ignore*/}
<MyPage items={items} />
</>
);
};
The error I get is that cannot return promise to react
So my understanding is that I cannot export async component.
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
That is all fine, so my question is howcan I solve this situation then ?
The goal is still the same (items are fetched fine now but the component is returned as promise due to async and rest of react magic fails)
I have solved this issue myself and am posting the solution for anyone facing the same issue.
So summarized
You still import dynamically one react component, but the "localized" react component relies on the common page. For example page-en.tsx imports page.tsx (see below)
This will be your NextJs main page:
import React from 'react';
import dynamic from 'next/dynamic';
const Page = ({ t, ...props }: Props) => {
const locale = props._nextI18Next.initialLocale;
const nt = scopedT(t, T_NAMESPACE);
const DynamicComponent = dynamic(() => import(`../page-data/mypage/${locale}`));
return (
<>
<Head>
<title>{nt('pageTitle')}</title>
</Head>
{/*#ts-ignore*/}
<DynamicComponent />
</>
);
};
and this will be your page-data/mypage/en|de|whateverlang.tsx
const En: React.FC = () => {
return <MyPage items={getItemsForLocale('en')} />;
};

Creating unique key props for array children (React.js)

I am trying to build out a component in React which takes information from a JSON source, and uses some of that information to create states which can be passed down into other separate components. While I haven't passed my states into separate components yet, I have been able to get my state to update with the information from the JSON. However, when I load my page I get an error code which I want to sort out before continuing with my project in case there are unintended side effects from leaving the error in my code. The error code reads as following:
index.js:1 Warning: Each child in a list should have a unique "key" prop.
Check the render method of FetchData
in div (at FetchData.js:27)
in FetchData (at App.js:8)
in div (at App.js:7)
My App.js looks like this:
import React from 'react';
import './App.css';
import FetchData from './Components/FetchData/FetchData';
function App() {
return (
<div className="App">
<FetchData/>
</div>
);
}
export default App;
My FetchData.js looks like this:
import React from 'react';
class FetchData extends React.Component {
constructor() {
super();
this.state = {
portrait: null,
title: null
};
}
componentDidMount() {
fetch('https://randomuser.me/api')
.then (response => {
return response.json();
})
.then (data => {
let userImage = data.results.map((person) => {
return (
<div>
<img alt='portrait' img src={person.picture.large}/>
</div>
)
})
let userTitle = data.results.map((person) => { //line 27
return (
<div key={person.results}>
<div> {person.name.title} </div>
</div>
)
})
this.setState ({
portrait: userImage,
title: userTitle
})
console.log(this.portrait, this.title)
})
}
render() {
return (
<div className='box1'>
<div className='box2'>
<h2>{this.state.title}</h2>
{this.state.portrait}
</div>
</div>
)
}
};
export default FetchData;
and just in case since my index.js looks like this:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.unregister();
I thought the issue was the fact that I used "person" into both my "data.results.map" so I tried to change the naming but that did not work either. Any help would be greatly appreciated!
The error is referring to your FetchData component.
The reconciliation algorithm in React can work if you assign an unique key to returned DOM objects. In this case, you are returning from the map function a list of similar DOM object. Any returned chunk have to declare the key attribute on the parent node.
In your case:
componentDidMount() {
fetch('https://randomuser.me/api')
.then (response => {
return response.json();
})
.then (data => {
let userImage = data.results.map((person) => {
return (
<div key={person.id}>
<img alt='portrait' img src={person.picture.large}/>
</div>
)
})
let userTitle = data.results.map((person) => { //line 27
return (
<div key={person.id}>
<div> {person.name.title} </div>
</div>
)
})
this.setState ({
portrait: userImage,
title: userTitle
})
console.log(this.portrait, this.title)
})
}
The key value must be an unique string and it is used by React to update the correct DOM nodes on state change. (I don't know how your person.results is filled, but you need a sort of ID)
For simple component you can also use this syntax
let userImage = data.results.map((person,idx) => {
return (
<div key={idx}>
<img alt='portrait' img src={person.picture.large}/>
</div>
)
})
Be aware using this syntax, because idx is the position of the current element in the containing array, and if used more than one time, it results in duplicate keys (And React will think that nodes with same key are the same nodes)
Using Index as a key is an anti-pattern in React.
Never using the index as a key in React, unless:
the list and items are static–they are not computed and do not change
the items in the list have no ids;
the list is never reordered or filtered.
When all of them are met, you may safely use the index as a key.
If not, please use the unique ID.
It may come from the elements you are going to display,
Or you can add a new ID property to your model or hash some parts of the content to generate a key.
The key only has to be unique among its siblings, not globally unique.
Read more here and here in React Docs
In your data.results.map((person)) function you need to add idx prop, so your code should look like:
let userImage = data.results.map((person,idx) => {
return (
<div key={idx}>
<img alt='portrait' img src={person.picture.large}/>
</div>
)
})
You can simply add the key attribute in the elements whenever you are using .map
let userImage = data.results.map((person, index) => {
return (
<div key={index}>
<img alt='portrait' img src={person.picture.large}/>
</div>
)
})
For the value of the key, it is preferred to use an unique id. If you do not have one, you can use index instead.

Data from Gatsby GraphQL always returns undefined?

Gatsby noob here so please bear with me. I have a component that accepts props from the index.js where it is supposed to receive data from an array of objects but will always receive the error TypeError: Cannot read property 'map' of undefined where it's referring to the Hero.js component index.js is calling for.
My assumption is that the data being queried in index.js is either not specific enough or that it is rendering the component before data is received. Here is the index.js file:
import { graphql } from 'gatsby';
import { Layout, SEO, Hero } from 'components';
const IndexPage = ({ data }) => {
const dataFetch = data.contentfulTemplateIndex.heroes;
let tester = () => {
for (let count = 0; count < dataFetch.length; count++) {
return <Hero {...props} />;
}
};
console.log(dataFetch);
let props = {
impactText: dataFetch.impactText,
labels: dataFetch.labels,
primaryLabel: dataFetch.primaryLabel,
location: dataFetch.location
// supportingText: dataFetch.supportingText.json
};
return (
<Layout>
{dataFetch && tester()}
</Layout>
);
};
export const query = graphql`
query {
contentfulTemplateIndex {
heroes {
image {
fluid {
src
}
}
impactText
labels
location
primaryLabel
supportingText {
json
}
}
}
}
`;
export default IndexPage;
Here is the Hero.js component which index.js is calling:
import { Link } from 'gatsby';
import { documentToReactComponents } from '#contentful/rich-text-react-renderer';
import cx from 'classnames';
import styles from './hero.module.scss';
const Hero = (props) => {
return (
<div>
<ul>
<Link className={styles.pills}>{props.primaryLabel}</Link>
{props.labels.map((label) => {
return <Link className={styles.pills}>{label}</Link>;
})}
</ul>
<div className={styles.grid}>
<h1>{props.impactText}</h1>
</div>
</div>
);
};
export default Hero;
It's impossible for an outsider to debug your code without a minimum reproducable example.
The best way to debug GraphQL is to use the GraphiQL interface of your browser.
Run gatsby develop. If it fails because of the TypeError remove the lines of code that cause the type error (but not the code of your GraphQL query!). You need to get your development server runnning.
Open your browser, use the URL: http://localhost:8000/___graphql
Copy your graphQL query from your code and paste it into the GraphiQL query window.
Can you access your data there? If not you made a mistake writing your query or the data is not where it's supposed to be.
This way you can make sure the data exists.
It also helps to console.log(props) so you can examine the data object:
const Hero = (props) => {
console.log(props);
return (

Display list of trails in React - "Warning: Failed prop type"

React Newbie
I am coding in React. I am taking an object of JSON data from a GET request to an api, and trying to pass it as a prop in a component. Then I am mapping over it to make a list of "trail" objects.
I am getting this error in the console:
"Warning: Failed prop type: Invalid prop trail of type array supplied to TrailItem, expected object."
Here's the code for my app level component:
import React, { Component } from "react";
import "./App.css";
import Navbar from "./components/layout/Navbar";
import Trails from "./components/trails/Trails";
import axios from "axios";
class App extends Component {
state = {
trails: {},
};
async componentDidMount() {
const res = await axios.get(
`https://www.hikingproject.com/data/get-trails?lat=35.0844&lon=-106.6504&maxDistance=10&key=${process.env.REACT_APP_HIKING_PROJECT_KEY}`
);
console.log(res.data);
this.setState({ trails: res.data });
}
render() {
return (
<div className='App'>
<Navbar />
<div>
<Trails trails={this.state.trails} />
</div>
</div>
);
}
}
export default App;
As far as I can tell, there is no problem with the data. A console.log(res.data); returns an object, so I know the api request is working.
Here's the code for my "Trails" component:
import React, { Component } from "react";
import TrailItem from "./TrailItem";
class Trails extends Component {
render() {
return (
<div style={trailStyle}>
{Object.keys(this.props.trails).map((key) => (
<TrailItem key={key} trail={this.props.trails[key]} />
))}
</div>
);
}
}
const trailStyle = {
display: "grid",
gridTemplateColumns: "repeat(3, 1fr)",
gridGap: "1rem",
};
export default Trails;
I feel like maybe I'm not using the correct syntax to step into the object, and then further into the "trails" array, but I'm stumped. Thank you for you help!
EDIT
Here is the "TrailItem" code:
import React from "react";
import PropTypes from "prop-types";
const TrailItem = ({ trail: { name, location, imgSmall } }) => {
return (
<div className='card text-center'>
<img src={imgSmall} alt='trail' style={{ width: "25%" }} />
<h3>{name}</h3>
<p>{location}</p>
</div>
);
};
TrailItem.propTypes = {
trail: PropTypes.array.isRequired,
};
export default TrailItem;
I followed the advice of one of the comments and changed the PropType to array, and that fixed one of the warnings. But I still can't get a list of <TrailItem />.
Inside you App render method put this at the start:
if (!this.state.trails.length) {
return <div>Loading...</div>;
}
You can give trails a default or initial value of an empty array so the map function can be invoked. By using an empty array the component will map over an the empty array and return an empty array to render.
Default Value:
const Trails = ({ trails }) => {
console.log(trails.trails);
return (
<div style={trailStyle}>
{trails.map(trail => <TrailItem key={trail.id} trail={trail} />)}
</div>
);
};
TrailItem.propTypes = {
trail: PropTypes.array.isRequired,
};
TrailItem.defaultValue = {
trail: [],
};
Initial Value:
const Trails = ({ trails = [] }) => {
console.log(trails.trails);
return (
<div style={trailStyle}>
{trails.map(trail => <TrailItem key={trail.id} trail={trail} />)}
</div>
);
};
TrailItem.propTypes = {
trail: PropTypes.array.isRequired,
};
Note: This won't fix passing a prop of the incorrect type, but the prop validation react does will. It sounds like you got that bit sorted out though.
I solved it! Granted, I may not have been clear in my original question, but I figured out why I couldn't get access to the data object from the API.
I needed to step into the object one more time upon receiving the response in my App component:
this.setState({ trails: res.data.trails });
Once I did that, in my Trails component I needed Object.key() to make turn the "trails" prop into an array so I could .map() over it.
And finally, the "tricky" part was that I needed to use each "key" as the index for each "trail" prop I was trying to pass to <TrailItem />:
{Object.keys(trails).map((key) => (
<TrailItem key={key} trail={trails[key]} />
))}

React valid ReactComponent must be returned. You may have returned undefined, an array or some other invalid object

I am building this simple react application, I encountered this error which I tried to debug but no joy yet. I am sure I must have been missing something important which may be quite obvious.
*Error: VideoDetail.render(): A valid ReactComponent must be returned. You may have returned undefined, an array or some other invalid object.*
index.js
import React, {
Component
} from 'react';
import ReactDOM from 'react-dom';
import YTSearch from 'youtube-api-search';
import VideoDetail from './components/video_details';
const API_KEY = 'XXXXXXXXXXXXXXXXXXXXXXX';
class App extends Component {
constructor(props) {
super(props);
this.state = {
videos: []
};
YTSearch({
key: API_KEY,
term: 'cute cats'
}, (videos) => {
this.setState({
videos
});
});
}
render() {
return (
<div >
<VideoDetail video = {this.state.videos[0]}/>
</div>
);
}
}
ReactDOM.render( < App / > , document.querySelector('.container'));
video_details.js
import React from 'react';
const VideoDetail = ({ video }) => {
if (!video) {
return (
<div>
Loading...
</div>
);
}
const videoId = video.id.videoId;
const url = `https://www.youtube.com/embed/${videoId}`;
return
(
<div className='video-details col-md-8'>
<div className='embed-responsive embed-responsive-16by9'>
<iframe className='embed-responsive-item' src={url}> </iframe>
<div className='details'>
<div> {video.snippet.title} </div>
<div> {video.snippet.description} </div>
</div>
</div>
</div>
);
}
module.exports = VideoDetail;
I must have been missing something that causes this error. What am I missing?
I found what the issue was. In video_details.js file, the second return I had, has nothing in front of it. So it should to be return ( instead. Leaving return on a single line without anything causes the error.
You forgot to import VideoDetail component.
Since you're already using ES6 use in the VideoDetail.js
export default VideoDetail;
and then import it:
import VideoDetail from "./VideoDetail";
The problem is with your VideoDetail react component.
Your return statement's bracket is in the next line. Because of that you end up returning undefined. Which is not a valid react component, hence you get the error.
In a sense, your react component end up looking something like this.
function VideoDetail() {
return
<div></div>;
}
This is not a valid javascript return statement.
If you want the return to acknowledge your below code, the bracket has to state from the same line, enclosing the below code.
function VideoDetail() {
return (
<div></div>
);
}

Categories