Rendering fetched data to screen - javascript

I built a component which uses Axios get request and retrieves a list of email addresses.
I don't know what should I write inside render() so I will be able to see the emails list over my website.
This is my suggestion:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {Link} from "react-router";
import axios from 'axios';
export class GetEmailsComponent extends Component {
state = {
emails: []
}
componentDidMount(){
//this.setState({emailList : undefined});
axios.get('./api/EmailAddresses')
.then(response => {
this.setState({emails: response.data});
console.log(response.data);
}).catch(function (error) {
console.log(error);
});
}
render() {
return (
<div>
<button type = "button" onClick= {this.state.emails.map(email => <div>{email}</div>)}>GET ALL EMAILS</button>
</div>
);
}
}
When I check the Console I see an array of the desired emails.
I am looking for a suggestion of how to edit my code so it will render all this mails to the screen (After the button clicked).
Thanks is advance.

Inside your render method, you can map over this.state.emails and return an element for each email (At first the array will be empty, so maybe you can add a condition so you wouldn't render an empty div for no reason) Something like:
render() {
return (
<div>
{this.state.emails.map(email => <div>{email}</div>)}
</div>
);
}
As for componentDidMount - It's a lifecycle method of React. Anything you put there will run after the component mounts for the first time. If you want to trigger the call to Axios once the button is clicked, define a different function (like fetchEmails) and call it using this.fetchEmails.

You have used a componentDidMount life cycle in react to fetch the data. And you called that method via a button. Normally we are not calling life cycle methods like this. i think its better to read the react documentation doc for get an idea about life cycles.
You can declare a function and can call that function via a button. Please find below answer.
class App extends Component {
constructor(props) {
super(props);
this.state = {
emails: [],
showEmails:false,
};
}
componentDidMount () {
axios
.get("./api/EmailAddresses")
.then(response => {
this.setState({ emails: response.data });
console.log(response.data);
})
.catch(function(error) {
console.log(error);
});
}
render() {
return (
<div>
<button type="button" onClick={() => this.setState({showEmail:true})}>
Get all mails
</button>
{this.state.showEmail && this.state.emails.map(email => <div>{email}</div>)}
</div>
);
}
}

Change your code to something like below.
You need to get emails when button is clicked so you need have custom event handler function for that but not componentDidMount method. You cannot call componentDidMount method as event handler function.
Also when you render emails in loop you need to set unique key to top element inside loop. Key can be a index or unique id from data. Looks like you don’t have unique id from emails array so you can use index as key like below
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {Link} from "react-router";
import axios from 'axios';
export class GetEmailsComponent extends Component {
state = {
emails: []
}
getEmails = () =>{
//this.setState({emailList : undefined});
axios.get('./api/EmailAddresses')
.then(response => {
this.setState({emails: response.data});
console.log(response.data);
}).catch(function (error) {
console.log(error);
});
}
render() {
return (
<div>
<ul>
{this.state.emails.map((email, index)=> <li key={"Key-"+index}>{email}</li>)}
</ul>
<button type="button" onClick={()=> this.getEmails()}>Get all mails</button>
</div>
)
}
}

Related

React not rendering properly

When changing my id (/movie/:id), i'm re rendering my whole component. Sometimes i have to click 3 or 4 times on my like to have a change and sometimes i have only to click once(but im one component behind).
Here is my code :
import React from "react";
import "../styles/DetailFilm.css"
import {Link} from 'react-router-dom';
const API_IMAGES = 'https://image.tmdb.org/t/p/w500';
class DetailFilm extends React.Component {
constructor(props) {
super(props);
this.state = {
id: props.movie_id,
info: {},
recommandation: []
}
}
componentDidMount() {
const fetchData = async () => {
//fetch api
this.setState({info: data,recommandation:data_recommandation_spliced })
}
fetchData();
}
componentWillReceiveProps(nextProps) {
console.log("RENDERING" + nextProps.movie_id)
const fetchData = async () => {
// fetch api
this.setState({id: nextProps.movie_id,info: data,recommandation:data_recommandation_spliced })
console.log("Rendered" + nextProps.movie_id)
}
fetchData();
}
render() {
return (
//css
{this.state.recommandation.map((movie) =>
<Link to={`/movie/${movie.id}`}>
<img src = {API_IMAGES + movie.poster_path} className="image-movie-genre"/>
</Link>
)}
)
}
}
export default DetailFilm;
Thanks for helping !
When adding JSX elements from an array, each one needs a unique key property so that React can keep track of necessary changes in the DOM. You need to add keys to your Link elements so that React will know to update them.
I found a solution which wasn't the one i was looking for at first.
I changed from using a Class to a function using useEffect avec id as param.

React Native this.state.questions.map() is not a function

It's my understanding that the most common use care for iterating over a list of data is map, which is an array method that iterates over an array, but when I tried to apply it here:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import axios from 'axios';
class QuestionList extends Component {
state = { questions: [] };
componentWillMount() {
axios
.get('https://opentdb.com/api.php?amount=10&difficulty=hard&type=boolean')
.then(response => this.setState({ questions: response.data }));
}
// renderQuestions() {
// return this.state.questions.map(question => <Text>{}</Text>);
// }
render() {
console.log(this.state);
return (
<View>
<Text>{}</Text>
</View>
);
}
}
export default QuestionList;
I ended up getting an error in the Simulator saying that this.state.questions.map() is not a function. I have searched for similar errors online, but they do not apply to my use case.
Keep in mind I commented out the code and erased what I had inside of <Text> because my machine was about to take off.
I don't know what this error means short of not being able to use the map() array helper method, does that mean I need to be applying a different helper method to iterate through this list of questions?
I did a console log of the response object like so:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import axios from 'axios';
class QuestionList extends Component {
state = { questions: [] };
componentWillMount() {
axios
.get('https://opentdb.com/api.php?amount=10&difficulty=hard&type=boolean')
.then(response => console.log(response));
}
render() {
console.log(this.state);
return (
<View>
<Text>{}</Text>
</View>
);
}
}
export default QuestionList;
and I got back the response object in the console:
from axios with a status of 200 which means the request was successful. You will notice I also go the data property and inside that is the results property and then the category with questions is inside of it:
So I am wondering if its that results property that I need to also implmement, but when I tried it I would get map() undefined.
Your API returns an object, which has no map method.
response.data.results is an array so change it to that if you intend to map over it:
this.setState({ questions: response.data.results }))
It's advisable to use componentDidMount instead of componentWillMount for async update.

ReactJS: I can not get the state to update with API data when the asynchronous API data fetch is completed

I am having a bit of an issue rendering components before the state is set to the data from a returned asynchronous API request. I have a fetch() method that fires off, returns data from an API, and then sets the state to this data. Here is that block of code that handles this:
class App extends Component {
constructor() {
super();
this.state = {
currentPrice: null,
};
}
componentDidMount() {
const getCurrentPrice = () => {
const url = 'https://api.coindesk.com/v1/bpi/currentprice.json';
fetch(url).then(data => data.json())
.then(currentPrice => {
this.setState = ({
currentPrice: currentPrice.bpi.USD.rate
})
console.log('API CALL', currentPrice.bpi.USD.rate);
}).catch((error) => {
console.log(error);
})
}
getCurrentPrice();
}
You will notice the console.log('API CALL', currentPrice.bpi.USD.rate) that I use to check if the API data is being returned, and it absolutely is. currentPrice.bpi.USD.rate returns an integer (2345.55 for example) right in the console as expected.
Great, so then I assumed that
this.setState = ({ currentPrice: currentPrice.bpi.USD.rate }) should set the state without an issue, since this data was received back successfully.
So I now render the components like so:
render() {
return (
<div>
<NavigationBar />
<PriceOverview data={this.state.currentPrice}/>
</div>
);
}
}
export default App;
With this, I was expecting to be able to access this data in my PriceOverview.js component like so: this.props.data
I have used console.log() to check this.props.data inside my PriceOverview.js component, and I am getting 'null' back as that is the default I set intially. The issue I am having is that the components render before the API fetch has ran it's course and updated the state with the returned data. So when App.js renders the PriceOverview.js component, it only passes currentPrice: null to it, because the asynchronous fetch() has not returned the data prior to rendering.
My confusion lies with this.setState. I have read that React will call render any time this.setState is called. So in my mind, once the fetch() request comes back, it calls this.setState and changes the state to the returned data. This in turn should cause a re-render and the new state data should be available. I would be lying if I didn't say I was confused here. I was assuming that once the fetch() returned, it would update the state with the requested data, and then that would trigger a re-render.
There has to be something obvious that I am missing here, but my inexperience leaves me alone.. cold.. in the dark throws of despair. I don't have an issue working with 'hard coded' data, as I can pass that around just fine without worry of when it returns. For example, if I set the state in App.js to this.state = { currentPrice: [254.55] }, then I can access it in PriceOverview.js via this.props.data with zero issue. It's the async API request that is getting me here, and I am afraid it has gotten the best of me tonight.
Here App.js in full:
import React, { Component } from 'react';
import './components/css/App.css';
import NavigationBar from './components/NavigationBar';
import PriceOverview from './components/PriceOverview';
class App extends Component {
constructor() {
super();
this.state = {
currentPrice: null,
};
}
componentDidMount() {
const getCurrentPrice = () => {
const url = 'https://api.coindesk.com/v1/bpi/currentprice.json';
fetch(url).then(data => data.json())
.then(currentPrice => {
this.setState = ({
currentPrice: currentPrice.bpi.USD.rate
})
console.log('API CALL', currentPrice.bpi);
}).catch((error) => {
console.log(error);
})
}
getCurrentPrice();
}
render() {
return (
<div>
<NavigationBar />
<PriceOverview data={this.state.currentPrice}/>
</div>
);
}
}
export default App;
Here is PriceOverview.js in full:
import React, { Component } from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
class PriceOverview extends Component {
constructor(props) {
super(props);
this.state = {
currentPrice: this.props.data
}
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.state.currentPrice != null ? <div className="price">{this.state.currentPrice}</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}
export default PriceOverview;
Thank you in advance to any help, it's much appreciated.
this.setState ({
currentPrice: currentPrice.bpi.USD.rate
})
Do not put an = in this.setState
Ok First thing, when you're writting code on React the components that hold state are the class base components so ... What I see here is that you're creating two class base components so when you pass down props from your app class component to your PriceOverview wich is another class base component you're essentially doing nothing... Because when your constructor on your PriceOverview get call you're creating a new state on that Component and the previous state ( that's is the one you want to pass down) is being overwritten and that's why you're seem null when you want to display it. So it should work if you just change your PriveOverview component to a function base component ( or a dumb component). So this way when you pass down the state via props, you're displaying the correct state inside of your div. This is how would look like.
import React from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
const PriceOverview = (data) => {
return (
<div className="overviewBar">
<div className="currentPrice panel">
//Im calling data here because that's the name you gave it as ref
//No need to use 'this.props' you only use that to pass down props
{data != null ? <div className="price">
{data}</div> : <div className="price">Loading...</div>
}
</div>
</div>
)
}
}
export default PriceOverview;
Whenever you're writing new components start always with function base components if you component is just returning markup in it and you need to pass some data go to his parent component update it (making the api calls there or setting the state there) and pass down the props you want to render via ref. Read the React docs as much as you can, hope this explanation was useful (my apologies in advance if you don't understand quite well 'cause of my grammar I've to work on that)
The thing is constructor of any JS class is called only once. It is the render method that is called whenever you call this.setState.
So basically you are setting currentPrice to null for once and all in constructor and then accessing it using state so it will always be null.
Better approch would be using props.
You can do something like this in your PriceOverview.js.
import React, { Component } from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
class PriceOverview extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.props.data!= null ? <div className="price">{this.props.data}</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}
export default PriceOverview;
Or you can use react lifecycle method componentWillReceiveProps to update the state of PriceOverview.js
componentWillReceiveProps(nextProps) {
this.setState({
currentPrice:nextProps.data
});
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.state.currentPrice != null ? <div className="price">{this.state.currentPrice }</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}

fetch JSON (http get) not working

I want run it using http get, but it not show nothing, Where is the error?. Angular http.get easier to get JSON and doing ngFor and show, but on React is little special. So, in conclusion I don't like do a simple "import data from './data.json'", I need load json from the cloud.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
class App extends Component {
// 1.JSON
constructor(props) {
super(props);
this.state = {
items: [],
};
}
// 2. JSON
componentJSON() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then(data => this.setState({ items: data.items }))
}
render() {
// this.componentJSON = this.componentJSON.bind(this);
this.setState({ items: data})
// 3. JSON
// const { items } = this.state;
return (
<Router>
<div className="App">
<ul>
{items.map(item =>
<li key={item.title}>
{item.title}
</li>
)}
</ul>
</div>
</Router>
);
}
}
export default App;
Working now!,
Thanks anyway friends!
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.state = {
items : []
};
// You should bind this object to componentWillMount method, other setState was not working
this.componentWillMount = this.componentWillMount.bind(this);
}
// This method is call before component will mounted
componentWillMount() {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then( data => this.setState({ items : data }) );
}
render() {
const { items } = this.state;
return (
<ul>
{items.map(item =>
<li key={item.title}>
{item.title}
</li>
)}
</ul>
);
}
}
export default App;
In your function you are assuming that the context is the class, but its not, its not how js works, so when you are trying to use this.setState it would not work because the context of the function doesnt have any function called setState.
A simple way of solving this is binding the function to the class, by simply adding the following line in the ctor:
this.componentJSON = this.componentJSON.bind(this);
You need to call your componentJSON function.
It is best to do this within componentDidMount()
componentDidMount(){
this.componentJSON()
}
This will get called when the component is rendered in the browser.
It is a common mistake to call your API within componentWillMount() but this will make your API call happen twice.
As mentioned in my other comment, be careful about calling your API in your render function as it will mean that you API is called every time there is a re-render. A re-render happens after setting state and since you are setting state in your API call, it will cause an infinite loop
Use componentDidMount
componentDidMount ()
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then(data => this.setState({ hits: data.hits }))
}

Component is not receiving current, but previous input values

I have two components to represent a list of articles and a filtering form. Every time any form field is changed, I need to send a HTTP request including the selected filters.
I have the following code for the SearchForm:
import React from 'react';
import { reduxForm, Field } from 'redux-form';
const SearchForm = ({ onFormChange }) => (
<form>
<Field component='select' name='status' onChange={onFormChange}>
<option>All</option>
<option value='published'>Published</option>
<option value='draft'>Draft</option>
</Field>
<Field
component='input'
type='text'
placeholder='Containing'
onChange={onFormChange}
/>
</form>
);
export default reduxForm({ form: 'myCustomForm' })(SearchForm);
And the following for the PostsList:
import React, { Component } from 'react';
import SearchForm from './SearchForm';
import { dispatch } from 'redux';
class PostsList extends Component {
constructor(props) {
super();
this.onFormChange = this.onFormChange.bind(this);
}
onFormChange() {
// Here I need to make the HTTP Call.
console.info(this.props.myCustomForm.values);
}
componentWillMount() {
this.props.actions.fetchArticles();
}
render() {
return (
<div>
<SearchForm onFormChange={this.onFormChange} />
<ul>
{ this.props.articles.map((article) => (<li>{article.title}</li>)) }
</ul>
</div>
);
}
}
const mapStateToProps = (state) => ({
myCustomForm: state.form.myCustomForm
});
const mapDispatchToProps = (dispatch) => ({
actions: {
fetchArticles: dispatch({ type: 'FETCH_ARTICLES' })
}
});
export default connect(mapStateToProps, mapDispatchToProps)(PostsList);
Though there is nothing going wrong with the rendering itself, something very awkful is happending with the myCustomForm.values prop when I change the form.
When I do that for the first time, the console.log(this.props.myCustomForm.values) call returns undefined, and the next calls return the previous value.
For example:
I load the page and select the draft option. undefined is printed.
I select published. { status: 'draft' } is printed.
I select draft again... { status: 'published' } is printed.
I inspected the redux store and the componend props. Both change according to the form interaction. But my function is returning the previous, not the new value sent by onChange.
This is clearly a problem with my code, most probably with the way I'm passing the function from parent to child component.
What am I doing wrong?
There is nothing wrong with your function. What I think is happening is that first time you select the option your callback is fired and is console logging current state for myCustomForm.values which haven't been yet changed by redux-form. So when the select changes:
your callback is fired...
...then redux-form is updating the state.
So. when your callback is making console.log it's printing not yet updated store.
do this, and you will see it's true:
onFormChange(e) {
// Here I need to make the HTTP Call.
console.info(e.currentTarget.value);
}
EDIT
My first question would be, do you really need to store this value in redux and use redux-form? It's a simple case, and you get current value in a way I showed you above.
However, if that's not the case, the callback is not required here, you just need to detect in your connected component (PostsList) that values have been changed in a form. You can achieve it with componentWillReceiveProps hook.
class PostsList extends Component {
constructor(props) {
super(props); // you should pass props to parent constructor
this.onFormChange = this.onFormChange.bind(this);
}
componentWillReceiveProps(nextProps) {
if(this.props.myCustomForm.values !== nextProps.myCustomForm.values) {
// do your ajax here....
}
}
componentWillMount(nextProps) {
this.props.actions.fetchArticles();
}
render() {
return (
<div>
<SearchForm />
<ul>
{ this.props.articles.map((article) => (<li>{article.title}</li>)) }
</ul>
</div>
);
}
}

Categories