How to fetch data from api and pass it as props - reactjs

I'm in the learning phase of react and trying to figure out how to
fetch api data and pass it as props, so i created my own api file in
github and tried to fetch the api data from it, here is the link
below:
https://raw.githubusercontent.com/faizalsharn/jokes_api/master/jokesData.js
for some reason the data is not being fetched from the api and not
being passed as props could someone, please explain me where im doing
wrong, forgive me if there is any obvious mistakes here im still in
beginner level
App.js
import React, {Component} from "react"
import Joke from "./joke"
class App extends Component {
constructor() {
super()
this.state = {
loading: false,
jokeComponents: []
}
}
componentDidMount() {
this.setState({loading: true})
fetch("https://raw.githubusercontent.com/faizalsharn/jokes_api/master/jokesData.js")
.then(response => response.json())
.then(data => {
this.setState({
loading: false,
jokeComponents: data.jokesData.map(joke => <Joke key={joke.id} question={joke.question} punchLine={joke.punchLine} />)
})
})
}
render() {
const text = this.state.loading ? "loading..." : this.state.jokeComponents
return (
<div>
{text}
</div>
)
}
}
export default App
joke.js
import React from "react"
function Joke(props) {
return (
<div>
<h3 style={{display: !props.question && "none"}}>Question: {props.question}</h3>
<h3 style={{color: !props.question && "#888888"}}>Answer: {props.punchLine}</h3>
<hr/>
</div>
)
}
export default Joke

I check the API, and found out that it is not working properly when the response.json() is being invoke in the fetch API.
And this is due to the error in the response of the API. You just need to return a bare array, and not return the API with a variable.
For reference, please check the return json of the Jsonplaceholder Fake API. https://jsonplaceholder.typicode.com/posts
Hope this fix your error.
Also, for the state of the jokeComponents, please just have the array passed in the response, and not manipulate the data. Just use the .map for the jokeArray in the render() function if the state is changed. :)

To show content after it is being loaded and hide the loading indicator, use a function that simulates an async action and after that the data will be shown. I've shown this example with another API, as there is a problem with your API. I hope you fix that. Also set headers to allow cross domain data access.
App.js
import React, { Component } from "react";
import ReactDOM from "react-dom";
import Joke from "./Joke";
class App extends Component {
constructor() {
super();
this.state = {
loading: true,
jokeComponents: []
};
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/posts",{
headers: { crossDomain: true, "Content-Type": "application/json" }
}).then(response=>response.json())
.then(data => {
console.log(data);
this.setState({
jokeComponents: data.map(joke => (
<Joke
key={joke.id}
question={joke.title}
punchLine={joke.body}
/>
))
});
});
demoAsyncCall().then(() => this.setState({ loading: false }));
}
render() {
const { loading } = this.state;
if(loading) {
return "loading...";
}
return <div>{this.state.jokeComponents}</div>;
}
}
function demoAsyncCall() {
return new Promise((resolve) => setTimeout(() => resolve(), 2500));
}
ReactDOM.render(<App />, document.getElementById('root'));
The working code of the same is set up in CodeSandbox below:

Gideon Arces correctly explained your first bug, but there's more to do:
You need to format your .json file as json, which is not the same as javascript.
For example, while this is javascript {id: 1, question: "?"} it's not json. Json must be formatted like this: {"id": "1", "question":"?"} with quotes around the property names.
You need to do your data fetching in your componentDidMount and call setState there
You need to pull data from the state and render your components in render(). Typically this is done by creating an array of components and then putting them into the return inside {}. See more on that here: Lists and Keys
It's always a good idea to start with dummy data hardcoded into your component before you try to combine your ui with your api. See below in the componentDidMount() where I hardcoded some jokes. This way you can isolate bugs in your ui code from those in your network/api code.
class App extends React.Component {
constructor() {
super();
this.state = {
loading: false,
jokes: []
};
}
componentDidMount() {
// this.setState({loading: true})
// fetch("https://raw.githubusercontent.com/faizalsharn/jokes_api/master/jokesData.js")
// .then(response => response.json())
// .then(data => {
// console.log(data);
// this.setState({
// loading: false,
// jokes: data
// })
// })
const json = `[
{
"id": "1",
"question": "?",
"punchLine": "It’s hard to explain puns to kleptomaniacs because they always take things literally."
},
{
"id": "2",
"question": "What's the best thing about Switzerland?",
"punchLine": "I don't know, but the flag is a big plus!"
}
]`;
const jokes = JSON.parse(json);
this.setState({ jokes });
}
render() {
const jokeComponents = this.state.jokes.map(joke => (
<Joke key={joke.id} question={joke.question} punchLine={joke.punchLine} />
));
console.log(jokeComponents);
const text = this.state.loading ? "loading..." : jokeComponents;
return <div>Text: {text}</div>;
}
}
function Joke(props) {
console.log("joke props:", props);
return (
<div>
<h3 style={{ display: !props.question && "none" }}>
Question: {props.question}
</h3>
<h3 style={{ color: !props.question && "#888888" }}>
Answer: {props.punchLine}
</h3>
<hr />
</div>
);
}

Related

Why is the data from my API is not display

import React, { Component } from 'react'
class Newsgenerator extends Component {
constructor(){
super()
this.state = {
data:{}
}
}
componentDidMount() {
fetch("https://newsapi.org/v2/everything?q=Apple&from=2021-06-24&sortBy=popularity&apiKey={...}")
.then(response => response.json())
.then(news => {
this.setState({
data: news
})
console.log(news)
})
}
render() {
return (
<div>
{this.state.data.discription}
</div>
)
}
}
export default Newsgenerator
You have a few things going on here. First off, this.state.data.discription doesn't exist. When your API call response, you get an object like this which you set to this.state.data:
{
status: "ok",
totalResults: 1588,
articles: [...]
}
So this.state.data.discription is undefined. So what you're seeing is the result of trying to print an undefined variable - which in React is nothing.
My guess is that you want to print the articles, which means your return statement should look more like this:
return (
<div>
{this.state.data.articles.map((article, idx) => {
return (<h2 key={`article-${idx}`}>{article.title}</h2>)
}}
</div>
)
What you have in this code is an array of articles in your state.data, and you're iterating through that to print the article title (title in your api response).
This is the starting point and should set you in the right direction.

Using ReactJs to fetch data from an api but getting a blank page with no error

Hello Guys kindly someone assist me with the issue i am having with my code. I am a newbie trying to learn react. i am trying to fetch data from an api. From the browser console i can see the data but when i try to return the data in the Dom i get a blank page. see my code below.
import React, { Component } from "react";
class FetchApi extends Component {
constructor(props) {
super(props);
this.state = {
person: []
};
}
componentDidMount() {
fetch("https://api.randomuser.me/")
.then(res => res.json())
.then(data => console.log(data))
.then(data => {
this.setState({
person: data
});
});
}
render() {
return (
<div>
{this.state.person &&
this.state.person.map(item => (
<li key={item.results.id}>
{item.results.gender} {item.results.location}
</li>
))}
</div>
);
}
}
export default FetchApi;
I have modified your code to the following. In some cases the way you are referencing the properties was wrong. Have made some changes in your componentDidMount and in the render method.
Sandbox for your reference: https://codesandbox.io/s/react-basic-example-nubu7
Hope this resolves
import React, { Component } from "react";
class App extends Component {
constructor(props) {
super(props);
this.state = {
person: []
};
}
componentDidMount() {
try {
fetch("https://api.randomuser.me/")
.then(res => res.json())
.then(data => {
this.setState({
person: data.results
});
});
} catch (e) {
console.log(e);
}
}
render() {
return (
<div>
{this.state.person &&
this.state.person.map(item => (
<li key={item.id.value}>
{item.gender} {item.location.city}
</li>
))}
</div>
);
}
}
export default App;
Check this codesandbox, the response from the fetch API is in this format
{
"results":[
{
"gender":"male",
"name":{
"title":"Monsieur",
"first":"Alois",
"last":"Fernandez"
},
"location":{
"street":{
"number":1856,
"name":"Rue Duquesne"
},
"city":"Untereggen",
"state":"Genève",
"country":"Switzerland",
"postcode":9650,
"coordinates":{
"latitude":"-50.1413",
"longitude":"-23.6337"
},
"timezone":{
"offset":"+5:30",
"description":"Bombay, Calcutta, Madras, New Delhi"
}
},
"email":"alois.fernandez#example.com",
"login":{
"uuid":"04b2ee45-cf07-4015-a5d8-2311f6dc28a1",
"username":"ticklishleopard228",
"password":"forward",
"salt":"V8bDcgux",
"md5":"d521c6488fc4644ccb7e670e9e67bc20",
"sha1":"9673afe219f27817c573a9cb727c209357d386ef",
"sha256":"c13a5bc77dc720a6cc46bc640680e9501225fc94c9bc6ba7fe203fe989a992a0"
},
"dob":{
"date":"1957-11-24T13:46:29.422Z",
"age":63
},
"registered":{
"date":"2003-05-18T19:56:11.397Z",
"age":17
},
"phone":"077 871 56 07",
"cell":"077 461 83 98",
"id":{
"name":"AVS",
"value":"756.1050.9271.56"
},
"picture":{
"large":"https://randomuser.me/api/portraits/men/8.jpg",
"medium":"https://randomuser.me/api/portraits/med/men/8.jpg",
"thumbnail":"https://randomuser.me/api/portraits/thumb/men/8.jpg"
},
"nat":"CH"
}
],
"info":{
"seed":"76a6b875b2ba81dd",
"results":1,
"page":1,
"version":"1.3"
}
}
So you have to set the person in the state to data.results instead of data and access the item in the results accordingly.

How do I only render one result in a separate component using axios in React?

Edit//
I suppose my question isn’t so clear. I’m trying to get one park returned when my url points to http://localhost:1233/details/‘${parkcode}’. I’ve defined the param for the url in my results.js file. But I’m having trouble in defining the this.setState in my details.js to render just one result of the park based on the id which also happens to be the park code.
I'm new to React (and possibly to JavaScript, I don't know anymore). I am following a tutorial - instead of using an npm package for an API I decided to branch out and use axios.get() to fetch data from an API. I am able to render the results from a component into the browser, however after adding on reach-router (I assume it's similar to React Router), I am having troubles rendering just one result of my API call as the page I am attempting to build is supposed to only show ONE result based on the ID I have defined.
In my main file, which is Results.js here, I am able to get the data with no problem and include them in my file using JSX and render them. I'm attempting to use the same logic as I did in that page in my Details.js page (which is the page that is supposed to show only one result to the ID in the route).
How I'm using axios in Results.js
componentDidMount() {
axios
.get(
"https://developer.nps.gov/api/v1/parks?stateCode=wa&fields=images&api_key=" +
`${nps}`
)
// https://css-tricks.com/using-data-in-react-with-the-fetch-api-and-axios/
.then(res =>
res.data.data.map(park => ({
description: `${park.description}`,
fullname: `${park.fullName}`,
states: `${park.states}`,
parkcode: `${park.parkCode}`,
image: `${park.images[0] ? park.images[0].url : "No Image"}`,
designation: `${park.designation}`
}))
)
.then(parks => {
this.setState({
parks
});
console.log(parks);
});
}
How I'm attempting to use the same logic in Details.js
It's not recognizing park.name even though I did the API call. However, if I hard code park[0].name it works. I have no idea what I'm doing wrong here. It might be an obvious problem but help me.
class Details extends React.Component {
constructor (props) {
super(props);
this.state = {
loading: true,
}
}
componentDidMount() {
axios
.get(
"https://developer.nps.gov/api/v1/parks?stateCode=wa&fields=images&api_key=" +
`${nps}`,
{ id: this.props.id }
).then(res => {
const park = res.data.data.map(park => ({
description: `${park.description}`,
fullname: `${park.fullName}`,
states: `${park.states}`,
parkcode: `${park.parkCode}`,
image: `${park.images[0] ? park.images[0].url : "No Image"}`,
designation: `${park.designation}`
}))
console.log(park.name);
this.setState({
name: park.name;
loading: false
})
}).catch(err => {
this.setState({error: err});
})
}
I'm expecting the page to recognize the id as defined in the GET request along with the axios, and render the park details in relation to the id. But now, it's doing none of it and I've been stuck on this for forever :(
There are some unnecessary parts in your code. You don't need to construct your data as you do in your setState part. You are getting park list and it is already a structured data. So, just set your state with the data you get back.
After that, you can map over this data and render the parks with links for React Router. You can use parkCode as your URL param for Link. In Details component you can extract this parkCode and make a new request for park details, then set this to your state.
I'm providing an example.
index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Results from "./Results";
import Details from "./Details";
const Routes = () => (
<Router>
<Switch>
<Route exact path="/" component={Results} />
<Route path="/details/:parkCode" component={Details} />
</Switch>
</Router>
);
ReactDOM.render(<Routes />, document.getElementById("root"));
Results
import React from "react";
import axios from "axios";
import { Link } from "react-router-dom";
class Results extends React.Component {
state = {
parks: [],
loading: true,
};
componentDidMount() {
axios(
"https://developer.nps.gov/api/v1/parks?stateCode=wa&fields=images&api_key=LbqZVj21QMimfJyAHbPAWabFaBmfaTZtseq5Yc6t"
).then(res => this.setState({ parks: res.data.data, loading: false }));
}
renderParks = () =>
this.state.parks.map(park => (
<Link to={`/details/${park.parkCode}`} key={park.parkCode}>
<div>{park.fullName}</div>
</Link>
));
render() {
return (
<div>{this.state.loading ? <p>Loading...</p> : this.renderParks()}</div>
);
}
}
export default Results;
Details
import React from "react";
import axios from "axios";
class Details extends React.Component {
state = { park: "", loading: true };
componentDidMount() {
const { match } = this.props;
axios(
`https://developer.nps.gov/api/v1/parks?parkCode=${match.params.parkCode}&api_key=${nps}`
).then(res => this.setState({ park: res.data.data[0], loading: false }));
}
render() {
return (
<div>
{this.state.loading ? <p>Loading...</p> : this.state.park.description}
</div>
);
}
}
export default Details;
You can try this in .then()
let park = res.data.data.map(park => ({
description: `${park.description}`,
fullname: `${park.fullName}`,
states: `${park.states}`,
parkcode: `${park.parkCode}`,
image: `${park.images[0] ? park.images[0].url : "No Image"}`,
designation: `${park.designation}`
}))
park = park[0]; // convert arrays of parks to single park
console.log(park.fullname); // now you can use `park.fullname`
or this
const park = {
description: `${res.data.data[0].description}`,
fullname: `${res.data.data[0].fullName}`,
states: `${res.data.data[0].states}`,
parkcode: `${res.data.data[0].parkCode}`,
image: `${res.data.data[0].images[0] ? park.images[0].url : "No Image"}`,
designation: `${res.data.data[0].designation}`
}
console.log(park.fullname); // now you can use `park.fullname`
otherwise do it in API
I think you can first set a state for your responses and then try to show them
same this :
state = {
result: []
}
componentDidMount() {
axios
.get("https://developer.nps.gov/api/v1/parks?stateCode=wa&fields=images&api_key=" +`${nps}`).then((res) => {
this.setState({result: res.data.data})
})
}
render(){
const result = this.state.result.map((el, index) => {
return(
//data
)
})
return(
<div>
{result}
</div>
)
}
I believe this is the part you are getting wrong
const parks = res.data.data.map(park => ({
description: `${park.description}`,
fullname: `${park.fullName}`,
states: `${park.states}`,
parkcode: `${park.parkCode}`,
image: `${park.images[0] ? park.images[0].url : "No Image"}`,
designation: `${park.designation}`
}))
console.log(parks) // this should display your array of all parks
this.setState({
parks,
loading: false
})
displayParks(parks) {
const allParks = parks.map((park, index) => {
return <div key={park.parkCode}>{park.fullname}<div>
})
}
render() {
const { parks } = this.state;
const displayParks = parks && parks.length > 0 ? this.displayParks(parks) : <div>Loading parks</div>
return (
<div>{ displayParks }</div>
);
}
When you do a .map on an array you are basically creating another array and that is what is returned to your park variable.
So in your render method, you can then loop over every item in parks

How to make React.js fetch data from api as state and pass this state data from its parent to child component

I am working on a React.js + D3.js project. I wanted App.js to fetch data from a json file and save this data into state and pass this parent sate data down to my child component state through the property. I found if I use static data in App.js works fine, but once fetching from a json file, it failed because no data can be stored into property. My App.js like this:
import React, { Component } from 'react';
import SandkeyGraph from './particle/SandkeyGraph';
class App extends Component {
state = {
data : null
}
// works fine in this way!
// state = {
// data: {
// "nodes":[
// {"node":0,"name":"node0"},
// {"node":1,"name":"node1"},
// {"node":2,"name":"node2"},
// {"node":3,"name":"node3"},
// {"node":4,"name":"node4"}
// ],
// "links":[
// {"source":0,"target":2,"value":2},
// {"source":1,"target":2,"value":2},
// {"source":1,"target":3,"value":2},
// {"source":0,"target":4,"value":2},
// {"source":2,"target":3,"value":2},
// {"source":2,"target":4,"value":2},
// {"source":3,"target":4,"value":4}
// ]}
// }
componentWillMount() {
this.getData('./data/sankey.json');
}
getData = (uri) => {
fetch(uri)
.then((response) => {
return response.json();
})
.then((data) => {
// successful got the data
console.log(data);
this.setState({ data });
});
}
render() {
// failed
const { data } = this.state;
return (
<div>
<SandkeyGraph
height={300}
width={700}
id="d3-sankey"
sankeyData = {this.state.data}
/>
</div>
);
}
}
export default App;
parrt of my is like this:
class SankeyGraph extends Component {
displayName: 'SankeyGraph';
state = {
sankeyData : null
}
constructor(props) {
super(props);
this.state.sankeyData = props.sankeyData || null;
}
PropTypes : {
id : PropTypes.string,
height: PropTypes.number,
width: PropTypes.number,
sankeyData : PropTypes.object,
}
componentDidMount () {
// will be null, if using fetch from App.js
//console.log(this.state.sankeyData);
this.setContext();
}
//...
Does anyone know how to handle this situation? Thank you so much in advanced!
After working out the problem, it turned out that there was no problem with fetch. It just didn't account for null in any of the components in the program (It would crash after using a null value.
For example in render:
render() {
if (this.state.data) {
return (
<div>
<SandkeyGraph
height={300}
width={700}
id="d3-sankey"
sankeyData = {this.state.data}
/>
</div>
);
}
else {
return <div/>
}
}
Or, the use of a ternary operator would work as well to be more concise (answer by #Eliran):
return (
{this.state.data ?
<div>
<SandkeyGraph
height={300}
width={700}
id="d3-sankey"
sankeyData = {this.state.data}
/>
</div> : <div>No Data Available</div>
);
You can add in your render function a condition:
render() {
// failed
const { data } = this.state;
return (
<div>
{data ?
<SandkeyGraph
height={300}
width={700}
id="d3-sankey"
sankeyData={data}
/> : "Loading..."
}
</div>
);
}
and only if data is populated the component will be rendered.
...
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
data : null
}
}
It seems like an error on the state declaration?
1.- Import your json in App component on top like this: import jsonData from './data/sankey.json'
2.- Set jsonData in state jsonData in App component.
constructor(props) {
super(props)
this.state = {
jsonData : {}
}
}
componentWillMount() {
this.setState({ jsonData })
}
You do not need to fetch as you have your json locally.
Once you have your json in your state, you can display it in render like this for example:
this.state.jsonData.data.links.map(x=>
<div>
<div>x.links.source</div>
<div>x.links.target</div>
</div>
)
I've been testing and you need to replace the getData() method to this:
getData = (uri) => {
fetch(uri, {
headers : {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
.then((response) => {
return response.json();
})
.then((data) => {
// successful got the data
console.log(data);
this.setState({ data });
});
}
This is because you need to declare 'Content-Type' and 'Accept' headers on your fetch options in order to make your request.

Cannot find state property after ComponentDidMount()

I want to practice my reactjs skills so I am doing some exercises with api calls to the popular openweathermap api
I am making the api call in the componentDidMount() cycle which following the documentation here, is the better place to make the async call
If I console.log(data) in render I first get undefined then I get the object that I need; but if I try to access said object with {data.city.name} which does exist I get the error "TypeError: Cannot read property 'name' of undefined" on the second re-render (componentDidMount() forces a second render)
I do not know what I am missing, I am not that experienced with lifecycles but it is pretty straightforward, I do not understand why this is happening
Can anyone enlighten me please? Thank you.
import React from 'react';
class WeekForecast extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
data: {}
};
}
componentDidMount() {
fetch([url])
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
data: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { data } = this.state;
return(
<div>
<p>This weeks forecast in {data.city.name}</p>
</div>
)
}
}
export default WeekForecast;
You are seeing this error because you are trying to get a property from an object which is undefined. If you try to log a property which is undefined at that time is not a problem, but if you try to get a property from this undefined object then you get an error.
const data = {};
console.log( "city is", data.city );
console.log( "city name is", data.city.name );
Here is another example. What if we don't define an empty object for data? You are defining one in your state, but what if we don't?
//const data = {};
console.log( "city is", data.city );
console.log( "city name is", data.city.name );
Here since data is not defined, we can't get the city as undefined.
In your situation, data is defined in your state as an empty object. So, trying to log the city returns an undefined, but trying to log city.name returns error.
Because your data is landing in your component after the first render you should check you have it with a conditional rendering. You will use this all the time when you are dealing with the data which is coming from a remote place. There are many ways doing the conditional rendering. Here is one of them:
renderHelper() {
const { data } = this.state;
if ( !data.city ) { return <p>Loading data...</p>}
return <p>This weeks forecast in {data.city.name}</p>
}
render() {
return (
<div>
{ this.renderHelper() }
</div>
)
}
Hope the below snippet helps.
Couple of issues.
The Promise handle was misused.
Handling the data on the firs render (data validation before operating).
class WeekForecast extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
data: {}
};
}
fetch = () => Promise.resolve({city:{name:"New York City"}})
componentDidMount() {
this.fetch()
.then(
(result) => {
this.setState({
isLoaded: true,
data: result
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { data } = this.state;
return(
<div>
<p>This weeks forecast in {data.city && data.city.name || 'No City'}</p>
</div>
)
}
}
ReactDOM.render(<WeekForecast />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>

Resources