API Call Returns Data But Not Render - reactjs

My console.log shows axios call returns data [object object] but it shows undefined when I try to render the data. Any ideas?
```
class CourseDetail extends Component {
state={
ID: this.props.match.params.ID,
course:[]};
componentDidMount(){
this.runSearch();
}
runSearch=async()=>{
const response= await axios.get('API\?{this.props.match.params.ID}')
this.setState({course: response.data});
//console.log shows course=[object object]
console.log("course="+response.data);
}
render(){
//course is undefined below
const course= this.state.course.map(item=> <div>(item.SUBJECT)</div>)
return (
<div>
{course}
</div>
); }
};
export default CourseDetail;

As #HolyMoly mentioned in the comments, you may need to stringify the response as JSON as well before logging it depending on what the API is returning. There's also a syntax error in your map function, you are using parentheses instead of curly braces to render the value. Depending on the structure of your data response, something like this may work:
class CourseDetail extends Component {
state={
ID: this.props.match.params.ID,
course:[]};
componentDidMount() {
axios.get('API\?{this.props.match.params.ID}')
.then(res => res.json())
.then(res => {
console.log("course="+res.data);
this.setState({course: res.data})
})
}
render(){
//course is undefined below
const course= this.state.course.map(item=> <div>{item.SUBJECT}</div>)
return (
<div>
{course}
</div>
); }
};
export default CourseDetail;
This depends on whether or not the data you are mapping is an object or an array of course. If it is in fact an object, you can map over the keys instead:
const course= Object.keys(this.state.course).map(item=> <div>{item.SUBJECT}</div>)

Related

React component sometimes don't show up

I'm in process of learning React (currently high order component) and I have one problem. In my exercise, component sometimes display data and sometimes don't. My code consist of two Component
DisplayList - component that show data passed by props
GetAndDisplayData - high order component that receive DisplayList component, url of API, and desired parameter (to take from API)
Code:
DisplayList.js
class DisplayList extends React.Component{
render(){
return(
<ul>
{this.props.data.map((input, i) => <li key={i}>{input}</li>)}
</ul>
);
}
}
GetAndDisplayData.js
const GetAndDisplayData = (DisplayList, urlOfData, parameterToGet) =>
class GetAndDisplayDataClass extends React.Component{
constructor(props){
super(props);
this.state = {
loading: true,
urlOfData: urlOfData,
parameterToGet: parameterToGet,
data: []
}
}
componentDidMount(){
this.getData(urlOfData, parameterToGet)
}
getData(urlOfData,parameterToGet){
fetch(urlOfData)
.then(data => data.json())
.then(jsonData => {
jsonData.map(input =>
this.state.data.push(eval("input."+parameterToGet))
);
})
this.setState({loading: false})
console.log(this.state.data)
}
render(){
if(this.state.loading){
return(<p>Data is loading</p>)
}else{
return(
<div>
<p>Data loaded</p>
<DisplayList data={this.state.data} />
</div>
);
}
}
}
And call of HOC
render(){
const GetData = GetAndDisplayData(DisplayList, "https://restcountries.eu/rest/v1/all", "name" );
return(
<div>
<GetData/>
</div>
);
I suppose that problem is something about asynchronous, beacuse if I use some short list of data everthing is working great, but if I use this API and list of 250 object in list, sometimes data don't show up (and sometimes does). What am I doing wrong?
As you already said, data loading is asynchronous, so you must update loading state variable inside the callback function :
componentDidMount(){
this.loadData(urlOfData, parameterToGet)
}
loadData(urlOfData, parameterToGet){
fetch(urlOfData)
.then(data => data.json())
.then(jsonData => {
// I didn't understand how you want to map the data
const data = jsonData.map(...);
console.log(data);
// you must update the state here
this.setState({loading: false, data: data});
});
}

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

Can we send the JSON obtained from an API as a child component, instead of each individual attribute of the object?

I have been trying to send the data obtained by an API call to a child component via state and it doesn't seem to work.
I have been sending each individual attribute of the object as a prop to the child component.
Is there a way to send the whole JSON response as a prop to a child component?
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {},
name: ""
};
}
componentWillMount() {
this.getWeather();
}
getWeather(city) {
fetch(
`https://api.apixu.com/v1/current.json?key=2da827a3ce074ddb85417374xxxxxx&q=paris`
)
.then(res => res.json())
.then(data => {
this.getData(data);
})
.catch(err => {
return Promise.reject();
});
}
getData(data) {
var location = data.location.name;
this.setState({ data: data, name: location });
console.log(this.state.name);
console.log(this.state.data);
}
render() {
return <Child name={this.state.name} data={this.state.data} />;
}
}
class Child extends React.Component {
render() {
var data = this.props.data;
return (
<div>
<h1>{this.props.name}</h1>
<h1> {data.current.cloud}</h1>
</div>
);
}
}
ReactDOM.render(<Parent />, document.getElementById("root"));
I expect the data object to also be passed to the child but it doesn't and I get a crash screen stating that the data object is undefined.
Is there a way to send the whole JSON obtained in an API call as a prop to a child component?
Your Child component will render before the getWeather api return the data. So this.props.data in Child component will be {}, app crash when you access data.current.cloud.
You need to check whether data is not empty and it has current property. So your code should be
class Child extends React.Component {
render() {
var data = this.props.data;
return (
<div>
<h1>{this.props.name}</h1>
<h1>{data && data.current ? data.current.cloud : ''}</h1>
</div>
);
}
}
It is always a best practice to do all API calls in method "ComponentDidMount" rather than "ComponentWillMount". This will do away with your checking that whether response came from API or not. Once the response comes, component will be re-rendered. So, you can do like below
componentDidMount() {
this.getWeather();
}
As an addition to #tien Duoung's comment,
You may want to add an extra state variable. You could call it fetching or loading. The purpose will be to at least display something while your api result is not ready. It could be like so:
this.state = {
data: {},
name: "",
fetching: true
}
In the .then of your getData method, once data.current is available, this.setState({ fetching: false })
getData(data) {
var location = data.location.name;
this.setState({ data: data, name: location, fetching: false });
console.log(this.state.name);
console.log(this.state.data);
}
Then pass fetching as a prop to the child component too, and when fetching is true, render a loader component or say a placeholder <h1>Loading...</h1>

Accessing nested json in React

I can successfully fetch json object from the api. However there seems to be a problem in updating the state.
After printing the object I get expected and desired result console.log(videos2.list[0]); gives 1st item from the list attribute of json object, you can check how the api looks under this link:
https://api.dailymotion.com/videos?fields=description,id,thumbnail_url,title,&limit=5&search=cars
However when updating state with setState property selectedVideo: videos.list[0] is undefined.
The code for the component:
class App extends Component {
constructor(props) {
super(props)
this.state = {
videos2:[],
selectedVideo:null
}
this.DMSearch()
}
DMSearch(){
fetch(`https://api.dailymotion.com/videos?fields=description,id,thumbnail_url,title,&limit=5&search=cars`)
.then(result => result.json())
.then(videos2 => {
console.log(videos2.list[0]);
this.setState({
videos2: videos2.list,
selectedVideo: videos2.list[0]
});
console.log(selectedVideo);
});
}
render () {
const DMSearch = this.DMSearch()
return (
<div>
<SearchBar onSearchTermChange= {DMSearch}/>
<VideoDetail video={this.state.selectedVideo}/>
<VideoList
onVideoSelect={selectedVideo=>this.setState({selectedVideo})}
videos2={this.state.videos2}/>
</div>
)
}
}
And exact error is Uncaught (in promise) ReferenceError: selectedVideo is not defined
videos2:list[0] produces correct results, but when assigned to selectedVideo it is undefined
As requested I am also including the code for vide_list which uses the objects form parent component which might be producing error here:
const VideoList = (props) => {
const videoItems = props.videos.map((video)=>{
return (
<VideoListItem
onVideoSelect={props.onVideoSelect}
key={video.etag}
video={video} />
)
})
return (
<ul className="col-md-4 list-group">
{videoItems}
</ul>
)
}
To be specific, this line
const videoItems = props.videos.map((video)=> {
causes the error when reading props. I believe this has something to do with selectedVideo being null...
There is an issue at this line
console.log(selectedVideo);
You printed out an undefined variable. It should be this.state.selectedVideo.
The error selectedVideo is not defined happens because selectedVideo is not a variable, it is a key on the javascript object passed to this.setState function.
//...
this.setState({
videos2: videos2.list,
selectedVideo: videos2.list[0]
});
//...
You can mitigate the error by creating a variable:
DMSearch(){
fetch(`https://api.dailymotion.com/videos?fields=description,id,thumbnail_url,title,&limit=5&search=cars`)
.then(result => result.json())
.then(videos2 => {
console.log(videos2.list[0]);
const selectedVideo = videos2.list[0]; // create variable
this.setState({
videos2: videos2.list,
selectedVideo: selectedVideo
});
console.log(selectedVideo);
});
}

What is best approach to set data to component from API in React JS

We have product detail page which contains multiple component in single page.
Product Component looks like:
class Product extends Component {
render() {
return (
<div>
<Searchbar/>
<Gallery/>
<Video/>
<Details/>
<Contact/>
<SimilarProd/>
<OtherProd/>
</div>
);
}
}
Here we have 3 APIs for
- Details
- Similar Product
- Other Products
Now from Detail API we need to set data to these components
<Gallery/>
<Video/>
<Details/>
<Contact/>
In which component we need to make a call to API and how to set data to other components. Lets say we need to assign a,b,c,d value to each component
componentWillMount(props) {
fetch('/deatail.json').then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong ...');
}
})
.then(data => this.setState({ data, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
OR
Do we need to create separate api for each components?
Since it's three different components you need to make the call in the component where all the components meet. And pass down the state from the parent component to child components. If your app is dynamic then you should use "Redux" or "MobX" for state management. I personally advise you to use Redux
class ParentComponent extends React.PureComponent {
constructor (props) {
super(props);
this.state = {
gallery: '',
similarPdts: '',
otherPdts: ''
}
}
componentWillMount () {
//make api call and set data
}
render () {
//render your all components
}
}
The Product component is the best place to place your API call because it's the common ancestor for all the components that need that data.
I'd recommend that you move the actual call out of the component, and into a common place with all API calls.
Anyways, something like this is what you're looking for:
import React from "react";
import { render } from "react-dom";
import {
SearchBar,
Gallery,
Video,
Details,
Contact,
SimilarProd,
OtherProd
} from "./components/components";
class Product extends React.Component {
constructor(props) {
super(props);
// Set default values for state
this.state = {
data: {
a: 1,
b: 2,
c: 3,
d: 4
},
error: null,
isLoading: true
};
}
componentWillMount() {
this.loadData();
}
loadData() {
fetch('/detail.json')
.then(response => {
// if (response.ok) {
// return response.json();
// } else {
// throw new Error('Something went wrong ...');
// }
return Promise.resolve({
a: 5,
b: 6,
c: 7,
d: 8
});
})
.then(data => this.setState({ data, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
render() {
if (this.state.error) return <h1>Error</h1>;
if (this.state.isLoading) return <h1>Loading</h1>;
const data = this.state.data;
return (
<div>
<SearchBar/>
<Gallery a={data.a} b={data.b} c={data.c} d={data.d} />
<Video a={data.a} b={data.b} c={data.c} d={data.d} />
<Details a={data.a} b={data.b} c={data.c} d={data.d} />
<Contact a={data.a} b={data.b} c={data.c} d={data.d} />
<SimilarProd/>
<OtherProd/>
</div>
);
}
}
render(<Product />, document.getElementById("root"));
Working example here:
https://codesandbox.io/s/ymj07k6jrv
You API calls will be in the product component. Catering your need to best practices, I want to make sure that you are using an implementation of FLUX architecture for data flow. If not do visit phrontend
You should send you API calls in componentWillMount() having your state a loading indicator that will render a loader till the data is not fetched.
Each of your Components should be watching the state for their respective data. Let say you have a state like {loading:true, galleryData:{}, details:{}, simProducts:{}, otherProducts:{}}. In render the similar products component should render if it finds the respective data in state. What you have to do is to just update the state whenever you receive the data.
Here is the working code snippet:
ProductComponent:
import React from 'react';
import SampleStore from '/storepath/SampleStore';
export default class ParentComponent extends React.Component {
constructor (props) {
super(props);
this.state = {
loading:true,
}
}
componentWillMount () {
//Bind Store or network callback function
this.handleResponse = this.handleResponse
//API call here.
}
handleResponse(response){
// check Response Validity and update state
// if you have multiple APIs so you can have a API request identifier that will tell you which data to expect.
if(response.err){
//retry or show error message
}else{
this.state.loading = false;
//set data here in state either for similar products or other products and just call setState(this.state)
this.state.similarProducts = response.data.simProds;
this.setState(this.state);
}
}
render () {
return(
<div>
{this.state.loading} ? <LoaderComponent/> :
<div>
<Searchbar/>
<Gallery/>
<Video/>
<Details/>
<Contact/>
{this.state.similarProducts && <SimilarProd data={this.state.similarProducts}/>}
{this.state.otherProducts && <OtherProd data={this.state.otherProducts}/>}
</div>
</div>
);
}
}
Just keep on setting the data in the state as soon as you are receiving it and render you components should be state aware.
In which component we need to make a call to API and how to set data
to other components.
The API call should be made in the Product component as explained in the other answers.Now for setting up data considering you need to make 3 API calls(Details, Similar Product, Other Products) what you can do is execute the below logic in componentDidMount() :
var apiRequest1 = fetch('/detail.json').then((response) => {
this.setState({detailData: response.json()})
return response.json();
});
var apiRequest2 = fetch('/similarProduct.json').then((response) => { //The endpoint I am just faking it
this.setState({similarProductData: response.json()})
return response.json();
});
var apiRequest3 = fetch('/otherProduct.json').then((response) => { //Same here
this.setState({otherProductData: response.json()})
return response.json();
});
Promise.all([apiRequest1,apiRequest2, apiRequest3]).then((data) => {
console.log(data) //It will be an array of response
//You can set the state here too.
});
Another shorter way will be:
const urls = ['details.json', 'similarProducts.json', 'otherProducts.json'];
// separate function to make code more clear
const grabContent = url => fetch(url).then(res => res.json())
Promise.all(urls.map(grabContent)).then((response) => {
this.setState({detailData: response[0]})
this.setState({similarProductData: response[1]})
this.setState({otherProductData: response[2]})
});
And then in your Product render() funtion you can pass the API data as
class Product extends Component {
render() {
return (
<div>
<Searchbar/>
<Gallery/>
<Video/>
<Details details={this.state.detailData}/>
<Contact/>
<SimilarProd similar={this.state.similarProductData}/>
<OtherProd other={this.state.otherProductData}/>
</div>
);
}
}
And in the respective component you can access the data as :
this.props.details //Considering in details component.

Resources