Binding data from a promise to a react component is inaccessible - reactjs

I have two classes, one being a child component called article. I want to pass data to the article component after receiving data in the parent, which is my app.js. However, I am unable to drill down into the data from the response that I am setting in the App constructor--the data I am trying to access is undefined, but I am able to print the full response out fine, including the data that I am unable to drill down into. I would like to tie the articles array from the following JSON to my respective article components.
Data is being pulled from: https://newsapi.org/v2/top-headlines?sources=google-news&apiKey=edd0276dc8344c2abaeb40a3f6fb439f
class Article extends Component {
constructor(props) {
super(props);
this.props.title = props.title;
this.props.description = props.description;
this.props.url = props.url;
}
render() {
let pic = {
uri: 'https://cdn.vox-cdn.com/thumbor/M8Nb8mKymSEN59T2iDIe5XXiNTw=/0x146:2040x1214/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/11738447/VRG_ILLO_2761_Spacecraft_Sidebyside.jpg'
};
return (
<View style={styles.article}>
<Header/>
<Text>Two rockets launched within 15 minutes of each other Wednesday morning</Text>
<Image source={pic} style={{ height: 250}}/>
<Text numberOfLines={3}>Early in the morning on July 24th, rocket enthusiasts will have the lucky experience of being able to watch two launches at roughly the same time. Around 7:30AM ET, SpaceX is slated to launch one of its Falcon 9 rockets from the California coast, while Europe…</Text>
</View>
);
}
}
export default class App extends Component {
constructor(props) {
super(props);
let url = 'https://newsapi.org/v2/top-headlines?sources=google-news&apiKey=edd0276dc8344c2abaeb40a3f6fb439f';
this.state = fetch(url)
.then(function(response) {
response.text().then(function(text) {
console.log(text, "TEXT!!!");
return text;
});
});
}
render() {
return (
<ScrollView>
<Article/>
</ScrollView>
);
}
}

You shouldn't really be doing your fetch in the constructor.
Here's a working example to get you started:
import React from "react";
import ReactDOM from "react-dom";
const Article = ({ title, desc, url }) => {
return (
<React.Fragment>
<h1>{title}</h1>
<p>{desc}</p>
<img src={url} />
</React.Fragment>
);
};
let url =
"https://newsapi.org/v2/top-headlines?sources=google-news&apiKey=edd0276dc8344c2abaeb40a3f6fb439f";
class App extends React.Component {
state = {
articles: []
};
componentDidMount() {
fetch(url)
.then(res => res.json())
.then(data => {
this.setState({
articles: data.articles
});
});
}
render() {
if (this.state.articles.length === 0) {
return "Loading..";
}
const firstArticle = this.state.articles[0];
const { title, description, url } = firstArticle;
return <Article title={title} desc={description} url={url} />;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Working CodeSandbox here.

A couple things wrong about this:
the return type of a Promise<T> is a Promise<T> unless you await, in which case it's T.
you don't assign to this.state, state changes in React are abstracted away via this.setState
Following,
this.state = fetch(url)...
should be changed to
fetch(url)
.then(response => response.text())
.then(text => this.setState({ text }))
pass in this.state.text (or rename it) as props to the child
edit: as Colin pointed out in the comments below, assigning to this.state is fine in the constructor (which you are doing). However, fetching data is better suited for post-mount lifecycle methods like componentDidMount

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});
});
}

React Component always reverts to the last item in array

This is a React/Redux app. I have two components. One nested in the other.
<UserReview>
<UserReviewItem>
</UserReview>
I am working with two APIs. I call one API to get a 'movieId', I use the 'movieId' to call a second API to retrieve an image. I am mapping over an array, but it seems like it is only returning the last element's movieId.
The wrapping component:
class UserReview extends Component {
componentDidMount() {
this.props.fetchAllReviews();
}
render() {
const allReviews = this.props.reviews.slice(-2).map((review, i) => {
return (
<UserReviewItem
username={review.username}
text={review.text}
key={review._id}
movieId={review.movieId}
/>
)
});
const mapStateToProps = state => ({
reviews: state.movies.reviews
})
Child Component:
class UserReviewItem extends Component {
componentDidMount() {
**this.props.fetchImage(this.props.movieId)**
}
render() {
return (
<div key={this.props.key}>
<img
src={`https://image.tmdb.org/t/p/original/${this.props.img}`}
/>
<div>
<h4>{this.props.username}</h4>
<p>{this.props.text}</p>
</div>
</div>
);
const mapStateToProps = state => ({
img: state.movies.img
})
I want a different image for every item in the array but I am getting the same image even though the usernames and texts are different.
A solution I tried but got the same result:
class UserReview extends Component {
componentDidMount() {
this.props.fetchAllReviews();
}
render() {
const allReviews = this.props.reviews.slice(-2).map((review, i) => {
return (
<UserReviewItem
username={review.username}
text={review.text}
key={review._id}
-------> movieId={this.props.reviews[i].movieId} <--------
/>
)
});
const mapStateToProps = state => ({
reviews: state.movies.reviews
})
You can try this way:
class UserReview extends Component {
componentDidMount() {
this.props.fetchAllReviews();
}
renderItems(){
const { reviews } = this.props
if (!reviews) return []
return reviews.map(review => <UserReviewItem
username={review.username}
text={review.text}
key={review._id}
movieId={review.movieId}
/>)
}
render() {
return (
this.props.reviews
? <div>{this.renderItems()}</div>
: <p>Loading...</p>
)
};
const mapStateToProps = state => ({
reviews: state.movies.reviews
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
basically in the renderItems function you destructure the props, get the reviews and map them. In your render function you set a loader if the views are not ready yet (you can use a loading prop if you are setting that up in your store), or call the list if the reviews are already fetched and ready.
I found the answer. Because the second call depends on information from the first call I have to chain the calls using .then()
using a fetch inside another fetch in javascript

Updating react component from sideBar menu

I have a simple react app that consists of 3 components:
1. SideBar that contains links
2. ItemList that contains data in a table
3. Wrapper that wraps both of them (I understood from some posts here that it is sometimes useful, as I want to update the ItemsList component after clicking on different links on the sideBar).
What I have working now:
In the main Wrapper component:
render() {
return (
<div>
<SideMenu handleClick={this.handleClick} />
<ItemsList url={this.state.currentUrl} />
</div>
);
}
as soon as the app starts, using componentDidMount() in ItemsList component, it fetches there the data, and display it. that works fine.
Problem is, when I click the links in the sideMenu component, I am changing the currentUrl in the state of the main wrapper, so then it will get re-rendered by the new url:
handleClick() {
this.setState({ currentUrl: 'here I put the new address to fetch from'});
}
but what gets fetched is data that is located in the previous url, not the one I had just changed it to.
Basically, right after I debug and check the state after I had changed it, the currentUrl stays the previous one, and then it re-renders the ItemList with the previous url.
my question is, how do I Change the content of the itemList, using this handleClick() method? Hopefully I will get some insights. thank you very much, I would appreciate your assistance.
the main wrapper Code:
class MainWrapper extends React.Component {
constructor() {
super();
this.state = {
currentUrl: 'current url to fetch from...',
data: []
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ currentUrl: 'the new url ' });
}
render() {
return (
<div>
<SideMenu handleClick={this.handleClick} />
<ItemsList url={this.state.currentUrl} />
</div>
);
}
}
my Itemlist Component:
class ItemsList extends Component {
constructor(props) {
super(props);
this.state = { url: props.url, data: [] };
}
componentDidMount() {
return fetch(this.state.url)
.then((response) => response.json())
.then((responseJson) => {
this.setState({ data: responseJson.data });
})
}
render() {
return (
displaying the table html tags..
}
</div>
)
}
}
You could the componentDidUpdate life cycle method in your itemList component. Each time the url changes, the listItem would presumably re-render based on what I understood from your question, so the componentDidUpdate method will fire. It is in this method where you can check for the new url and make the new request.
See more here.

Handling responded forms in react

I'm doing a simple project that has something like 3 forms and right now I start the component with empty Inputs and then request data from API to pre-populate the form using the componentWillMount() hook.
It works for me now but if someday my app need more and more data it would be annoying to do this everytime for any new form and I would like to know if there is any lib or pattern to help pre-populating forms without using any state container (Redux, mobx, and I really don't know if they are needed in this case).
It is better to do your data fetching in componentDidMount than in componentWillMount:
If you need to load data from a remote endpoint, this is a good place
to instantiate the network request.
If you want to reuse some data fetching logic without any external state you could use Component with render props or Higher Order Components.
For example:
function withData(fetchData) {
return BaseComponent => {
class WithData extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
componentDidMount(){
fetchData().then(response => {
this.setState({ data: response })
})
}
render(){
return <BaseComponent {...this.props} data={this.state.data} />
}
}
return WithData;
}
}
And later you can reuse this logic:
const DataList = ({ data }) => (
<ul>
{
data.map(item =>
<li>{item.name}</li>
)
}
</ul>
)
// passing Promises as a `data` producers
const UserDataList = withData(fetchUsers)(DataList);
const GroupDataList = withData(fetchGroups)(DataList);
const CatsDataList = withData(() => fetchAnimals('cats'))(DataList);
const ListOfEverything = () => (
<Container>
<UserDataList />
<GroupDataList />
<CatsDataList />
</Container>
)

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