React - Passing fetched data from API as props to components - javascript

Iam trying to understand and learn how to pass around data as props to other components to use. Iam trying to build a top-level hierarchy where the API Request is made in a class at top level and then the result is passed around to child components to be used as props and then in states.
The problem is that when i pass the result i get "Object Promise" in my child component. How do I access the data sent as props to child components?
As you can see in my App.js in my render() method that i created a component of the class API and pass the result from the fetchData() method as parameter to the component.
In my API.js class i used console.log to check the result but
the result i get from the logs are:
line 5: {dataObject: Promise}
line 10: undefined
App.js:
import API from './API';
class App extends Component {
componentDidMount(){
this.fetchData();
}
fetchData(){
const url = "https://randomuser.me/api/?results=50&nat=us,dk,fr,gb";
return fetch(url)
.then(response => response.json())
.then(parsedJSON => console.log(parsedJSON.results))
.catch(error => console.log(error));
}
render() {
return (
<div className="App">
<API dataObject={this.fetchData()}/>
</div>
);
}
}
export default App;
API.js
import React from 'react';
class API extends React.Component{
constructor(props){
console.log(props);
super(props);
this.state = {
dataObj:props.dataObject
};
console.log(this.state.dataObject)
}
render() {
return(
<p>""</p>
)
}
}
export default API;

Try changing App.js to this:
import API from './API';
class App extends Component {
componentDidMount(){
this.fetchData();
}
fetchData(){
const url = "https://randomuser.me/api/?results=50&nat=us,dk,fr,gb";
return fetch(url)
.then(response => response.json())
.then(parsedJSON => this.setState({results: parsedJSON.results}))
.catch(error => console.log(error));
}
render() {
return (
<div className="App">
<API dataObject={this.state.results}/>
</div>
);
}
}
export default App;
This makes sure you fetch the data in componentDidMount and it now uses state to store the data which then will be passed into your API component.

If anyone is looking for an answer using Hooks then this might help.
App.js
import API from './API';
function App(props) {
const [result, setResult] = React.useState({});
// similar to componentDidMount
React.useEffect(() => {
this.fetchData();
}, []);
fetchData() {
const url = "https://randomuser.me/api/?results=50&nat=us,dk,fr,gb";
fetch(url)
.then(response => setResult(response.json()))
.catch(error => console.log(error));
}
return (
<div className="App">
<API dataObject={result}/>
</div>
);
}
export default App;
API.js
import React from "react";
function API(props) {
const [result, setResult] = React.useState(props.dataObject);
React.useEffect(() => {
setResult(result);
}, [result]);
return <p>{result}</p>;
}
export default API;
Hope it helps! And let me know if anything is incorrect.

You should fetch data in componentDidMount and not in render. Fetching the data within render causes the API request to be repeated, every time the DOM is re-rendered by react.js.
After making the GET request to the API endpoint, first parse the data into a javascript object, then set the results to state using this.setState from within your component.
From there, you may pass the data held in state to child components as props in the render function.
For example:
const App = (props) =>
<ChildComponent />
class ChildComponent extends React.Component {
constructor(props){
super(props);
this.state = {
results: []
}
}
componentDidMount(){
fetch('/api/endpoint')
.then(res => res.json())
.then(results => this.setState({results})
}
render(){
return <GrandchildComponent {...this.state} />
}
}
const GrandchildComponent = (props) =>
<div>{props.results}</div>

Related

Fetch in React making call continuosly

I am fetching the data from json file but the issue is that when i checked network tab. It's being call continuosly what can i do to resolve the problem.
: Network Tab Image
class Banner extends Component {
constructor(props) {
super(props);
this.state = {
buttonLink:"",
bannerImg:""
};
}
render() {
const a = fetch("./logo.json").then(response => response.json())
.then((temp1)=> {this.setState({buttonLink:temp1.buttonLink,bannerImg:temp1.bannerImg});
});
return(....
<img src={this.state.bannerImg}></img>
....
)
in class component you can use some lifecycle methods like componentDidMount
so put your fetch method inside of a componentDidMount not in render method because whenever you call setState() method, it will call render method again,and inside of your render method you call api and setState again, that makes an inifinite loop, do something like this:
componentDidMount(){
const a = fetch("./logo.json").then(response => response.json())
.then((temp1)=> {this.setState({buttonLink:temp1.buttonLink,bannerImg:temp1.bannerImg});
}
You can't call fetch inside render function.
You can use functional component to easily fetch the data.
import React, { useEffect, useState } from 'react';
const Banner = () => {
const [image, setImage] = useState({ buttonLink: '', bannerImg: '' });
useEffect(() => {
fetch('./logo.json')
.then((response) => response.json())
.then((temp1) => {
setImage({ buttonLink: temp1.buttonLink, bannerImg: temp1.bannerImg });
});
}, []);
return <img src={image.bannerImg}></img>;
};
export default Banner;

How to re-render parent component on child component's button click

I am learning react and I would still consider myself to be a beginner. My goal is to click a button on my child component so that I could re render the parent component. This is the code I have.
Parent Component
import React, { Component } from 'react';
import Activity from './Components/Activity'
class App extends Component {
state = {
activity: ''
}
handleClick = () => {
// I have read that forceUpdate is discouraged but this is just an example
this.forceUpdate()
}
async componentDidMount() {
const url = 'http://www.boredapi.com/api/activity/'
const response = await fetch(url);
const data = await response.json();
this.setState({
activity: data.activity
})
}
render() {
return (
<div>
<Activity act={this.state.activity} click={this.handleClick}/>
</div>
);
}
}
export default App;
Child Component
import React, { Component } from 'react';
class Activity extends Component {
render() {
return (
<div>
<h1>Bored? Here is something to do</h1>
<p>{this.props.act}</p>
<button onClick={this.props.click}>Something Else</button>
</div>
);
}
}
export default Activity;
As you can see I am trying to click a button so that I could get another fetch and a different activity renders on my child component. I am trying to keep my child component stateless but if keeping it stateless doesn't make sense or is just plain wrong I would love to know.
You can try to move fetching function outside componentDidMount
for the example:
handleClick = () => {
this.fetchdata();
}
async fetchdata(){
const url = 'http://www.boredapi.com/api/activity/'
const response = await fetch(url);
const data = await response.json();
this.setState({
activity: data.activity
})
}
componentDidMount() {
this.fetchdata();
}
You can make a class method for fetching the new activity,
Call it after the app first mounted with componentDidMount() and again when you call it from the child component Activity.
You should mentioned in the your question that the response body is different in each request you make.
import React, { Component } from 'react';
import Activity from './Activity'
class App extends Component {
state = {
activity: ''
}
handleClick = () => {
this.getActivity()
}
componentDidMount() {
this.getActivity();
}
async getActivity() {
const url = 'https://www.boredapi.com/api/activity/'
const response = await fetch(url);
const data = await response.json();
this.setState({
activity: data.activity
})
}
render() {
console.log(this.state);
return (
<div>
<Activity act={this.state.activity} click={this.handleClick}/>
</div>
);
}
}
export default App;
Here is also a sandbox:
https://codesandbox.io/s/dreamy-noether-q98rf?fontsize=14&hidenavigation=1&theme=dark

How to show the identifier on the page?

I used react.js to connect the api.
And i want to show the identifier on the page.
Here is response to console.log(this.state.weathers.cwbopendata)
After i console.log(this.state.weathers.cwbopendata.identifier)
,I got the error
What should i do to show the identifier on the page?
Here is the code:
import React,{Component} from 'react';
class App extends Component {
constructor(){
super();
this.state = {
weathers: {},
};
}
componentDidMount(){
fetch('https://opendata.cwb.gov.tw/fileapi/v1/opendataapi/F-C0032-001?Authorization=CWB-BB78764B-9687-4C1C-B180-66CB616129E5&format=JSON')
.then(response=> response.json())
.then( JSON=> this.setState({weathers:JSON}))
}
render(){
return (
<div className="App">
{console.log(this.state.weathers.cwbopendata.identifier)}
</div>
);
}
}
export default App;
This is a classic problem many newcomers face. You need to add a state to let your component know that data fetching is in progress, is completed or there is an error. So the component can show real data when it successfully fetched it, until then something you can display to the UI to let users know that the App is fetching data... I'd write it something like:
import React, { Component } from "react";
class App extends Component {
constructor() {
super();
this.state = {
weathers: {},
isFetching: true
};
}
componentDidMount() {
fetch(
"https://opendata.cwb.gov.tw/fileapi/v1/opendataapi/F-C0032-001?Authorization=CWB-BB78764B-9687-4C1C-B180-66CB616129E5&format=JSON"
)
.then(response => response.json())
.then(json => this.setState({ weathers: json, isFetching: false }));
}
render() {
const { isFetching, weathers } = this.state;
return (
<div className="App">
{isFetching ? "Loading.." : weathers.cwbopendata.identifier}
</div>
);
}
}
export default App;
In your case you tried to render the data at first mount, and at this point of time weathers is just holding an empty object {}. Thus weathers.cwbopendata returns undefined, and undefined.identifier throws the error as you see in the browser console.
Initially render method is being called before componentDidMount.
By the time it was called, data hadn't been fetched yet.
So you should properly handle situation when you have empty state.
import React,{Component} from 'react';
class App extends Component {
constructor(){
super();
this.state = {
weathers: {},
};
}
componentDidMount(){
fetch('https://opendata.cwb.gov.tw/fileapi/v1/opendataapi/F-C0032-001?Authorization=CWB-BB78764B-9687-4C1C-B180-66CB616129E5&format=JSON')
.then(response=> response.json())
.then( JSON=> this.setState({weathers:JSON}))
}
render(){
return (
<div className="App">
{console.log(this.state.weathers.cwbopendata && this.state.weathers.cwbopendata.identifier)}
</div>
);
}
}
export default App;

How to send asynchronous state fetch to component?

I am currently working on a little app for fun. I ran into an issue with using axios and returning the response to my App component as updated state.
I then try to allow another component to use that piece of state, but I am not able to actually access the data. I can console.log(props) from within the List component, but I am not sure how to output the actual data as I am only able to output the promise results. I want to be able to output props.currentUser and have it be the googleId (using google Oauth2.0)..I am sure the solution is simple but, here is my code:
App.js ->
import React from 'react';
import helpers from '../helpers';
import List from './List';
class App extends React.Component{
state = {
currentUser: null
}
componentDidMount() {
this.setState(prevState => ({
currentUser: helpers.fetchUser()
}));
}
render() {
return (
<div>
<List currentUser={this.state.currentUser}/>
</div>
);
}
};
export default App;
helpers.js ->
import axios from 'axios';
var helpers = {
fetchUser: async () => {
const user = await axios.get('/api/current_user');
return user.data;
}
};
export default helpers;
List Component ->
import React from 'react';
const List = (props) => {
const renderContent = () => {
console.log(props);
return <li>{props.currentUser}</li>
}
renderContent();
return (
<div>
<h1>Grocery List</h1>
<ul>
</ul>
</div>
);
}
export default List;
Output ->
{currentUser: null}
{currentUser: Promise}
currentUser: Promise__proto__: Promise[[PromiseStatus]]: "resolved"
Because fetchUser is an async function, it returns a promise. Thus, in the App component, you have to call setState inside the .then of that promise, like so:
componentDidMount() {
helpers.fetchUser()
.then(data => {
this.setState(prevState => ({
currentUser: data
}));
});
}
Okay all you need to change is :
componentDidMount() {
this.setState(prevState => ({
currentUser: helpers.fetchUser()
}));
}
to
componentDidMount() {
helpers.fetchUser().then(data => {
this.setState(prevState => ({
currentUser: data
}));
})
}
WORKING DEMO (checkout the console)
NOTE : async await always returns the promise it just make
synchronousonus behaviour inside the async function but end ot the
function it will always returns the promise.

redux - same data fetch HOC component called multiple times

I have a React decorator component which is connected to the Redux store and I'm using it to dispatch an action (which is used to get some data from an API endpoint) and show a Loader Component. Then, once the data is fetched, it shows a wrapped component.
It looks like this:
const loadData = LoaderComponent => WrappedComponent => {
class loadDataHOC extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const {fetchData, isLoading} = this.props;
if(!isLoading){
fetchData();
}
}
render() {
const {data, isLoading} = this.props;
if (isLoading) {
return <LoaderComponent />;
}
return <WrappedComponent data={data} />;
}
}
const mapStateToProps = state => {
return {
data: getData(state),
isLoading: getIsLoading(state)
};
};
const mapDispatchToProps = dispatch => bindActionCreators({getData}, dispatch);
return connect(mapStateToProps, mapDispatchToProps)(loadDataHOC);
};
export default loadData;
This component is meant to be reusable so I can use it to fetch and store the same data from different presentational components. What I'd like to do now is to use this component in two different parts of the same view, like this:
const EnhancedComponent1 = loadData(Spinner)(MyPresentationalComponent1);
const EnhancedComponent2 = loadData(Spinner)(MyPresentationalComponent2)
The problem is that the two EnhancedComponent both fire fetchData() because they are mounted together and therefore the isLoading prop is false in both the function calls.
For now I've solved it by checking the isLoading prop inside the action so the second call is immediately stopped, but I'm not sure if this is the best way to deal with it.
const getData = () => (dispatch, getState) => {
if(getIsLoading(getState())) {
return;
}
dispatch(getData());
...
};
Another way to do it would be to create only one parent enhanced component just to fetch the data and then two presentational components down the tree that only access the state, but I'd like to fetch the data as close as possible to the presentational component.
Thanks

Categories

Resources