onclick prepare html when ajax response arrivers and render in react? - javascript

Hey I'm new is react my requirement is that when a user clicks on a button an ajax get request get fired to the
server and based of receieved response I have to prepare the html and display it.
below is my code it is not working .. it can be solved in jquery by using async: false but i don't have to use that
any idea how to solve using axios
import React from "react";
import axios from "axios"
class UserItems extends React.Component {
constructor(props) {
super(props);
this.state = {
useritem: ''
}
}
prepareHtmlOnAjaxResponse(){
var user_item = this.state.useritem
// html prepation done here
return preparedHtml;
}
getDatFromServeronclick() {
// getting user data from server is done in this function
// when data is receieved it is stored in a state
var self = this;
var promise = axios.get("http://localhost:4000/user/1/items.json")
promise.then(function (response) {
self.setState({ useritem: response.data.items })
self.prepareHtmlOnAjaxResponse() // prepare html
})
console.log("executing first and returning null")
}
render() {
var result = this.getDatFromServeronclick() // getting undefined value this has to be called onclick
return (
<div>
{result} / result is undefined
</div>
);
}
}
export default UserItems;

You have to use self.setState function instead of self.state assignment, otherwise React wouldn't trigger rerender of the component.
var promise = axios.get("http://localhost:4000/user/1/items.json")
promise.then(function (response) {
self.setState({ useritems: response.data.items })
})
From React's documentation:
NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
Then in your render function
<button onClick={() => this.getDatFromServeronclick() }> {this.state.useritems.map(user => user.title)} </button>
you can replace user.title with whatever keys your object useritems has.

Related

Handling undefined/null properties in components during first render

I'm learning react and it's great, but i've ran into an issue and i'm not sure what the best practice is to solve it.
I'm fetching data from an API in my componentDidMount(), then i'm setting some states with SetState().
Now the problem is that because the first render happens before my states have been set, im sending the initial state values into my components. Right now i'm setting them to empty arrays or empty Objects ({ type: Object, default: () => ({}) }).
Then i'm using ternary operator to check the .length or if the property has a value.
Is this the best practice or is there some other way that i'm unaware of?
I would love to get some help with this, so that i do the basics correctly right from the start.
Thanks!
I think the best practice is to tell the user that your data is still loading, then populate the fields with the real data. This approach has been advocated in various blog-posts. Robin Wieruch has a great write up on how to fetch data, with a specific example on how to handle loading data and errors and I will go through his example here. This approach is generally done in two parts.
Create an isLoading variable. This is a bolean. We initially set it to false, because nothing is loading, then set it to true when we try to fetch the data, and then back to false once the data is loaded.
We have to tell React what to render given the two isLoading states.
1. Setting the isLoading variable
Since you did not provide any code, I'll just follow Wieruch's example.
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataFromApi: null,
};
}
componentDidMount() {
fetch('https://api.mydomain.com')
.then(response => response.json())
.then(data => this.setState({ dataFromApi: data.dataFromApi }));
}
...
}
export default App;
Here we are using the browser's native fetch() api to get the data when the component mounts via the use of componentDidMount(). This should be quite similar to what you are doing now. Given that the fetch() method is asynchronous, the rest of the page will render and the state will be up dated once the data is received.
In order to tell the user that we are waiting for data to load, we simply add isLoading to our state. so the state becomes:
this.state = {
dataFromApi: null,
isLoading: false,
};
The state for isLoading is initially false because we haven't called fetch() yet. Right before we call fetch() inside componentDidMount() we set the state of isLoading to true, as such:
this.setState({ isLoading: true });
We then need to add a then() method to our fetch() Promise to set the state of isLoading to false, once the data has finished loading.
.then(data => this.setState({ dataFromAPi: data.dataFromApi, isLoading: false }));
The final code looks like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataFromApi: [],
isLoading: false,
};
}
componentDidMount() {
this.setState({ isLoading: true });
fetch('https://api.mydomain.com')
.then(response => response.json())
.then(data => this.setState({ dataFromApi: data.dataFromApi, isLoading: false }));
}
...
}
export default App;
2. Conditional Rendering
React allows for conditional rendering. We can use a simple if statement in our render() method to render the component based on the state of isLoading.
class App extends Component {
...
render() {
const { hits, isLoading } = this.state;
if (isLoading) {
return <p>Loading ...</p>;
}
return (
<ul>
{dataFromApi.map(data =>
<li key={data.objectID}>
<a href={data.url}>{data.title}</a>
</li>
)}
</ul>
);
}
}
Hope this helps.
It Depends.
suppose you are fetching books data from server.
here is how to do that.
state = {
books: null,
}
if, your backend api is correctly setup.
You will get either empty array for no books or array with some length
componentDidMount(){
getBooksFromServer().then(res => {
this.setState({
books: res.data
})
})
}
Now In Your render method
render() {
const { books } = this.state;
let renderData;
if(!books) {
renderData = <Spinner />
} else
if(books.length === 0) {
renderData = <EmptyScreen />
}
else {
renderData = <Books data = { books } />
}
return renderData;
}
If you are using offline data persistence In that case initially you won't have empty array.So This way of handling won't work.
To show the spinner you have to keep a variable loader in state.
and set it true before calling api and make it false when promise resolves or rejects.
finally read upon to state.
const {loader} = this.state;
if(loader) {
renderData = <Spinner />
}
I set initial state in constructor. You can of course set initial state of component as static value - empty array or object. I think better way is to set it using props. Therefore you can use you component like so <App items={[1,2,3]} /> or <App /> (which takes value of items from defaultProps object because you not pass it as prop).
Example:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: [], // or items: {...props.items}
};
}
async componentDidMount() {
const res = await this.props.getItems();
this.setState({items: res.data.items})
}
render() {
return <div></div>
}
};
App.defaultProps = {
items: []
}

Component not rerendering on state change?

Whenever setState() is called, the component doesn't seem to rerender. As you can see by my comments, the state does in fact change and render seems to be called again, but if I don't add that if statement and simply add a paragraph tag that displays the data it will give me an error. I'm sure I'm missing something simple, but any help is appreciated.
import React from "react";
import axios from "axios";
import { constants } from "../constants/constants";
const { baseURL, apiKey, userName } = constants;
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
user: []
};
}
componentDidMount() {
let getUserInfo = axios.create({
baseURL,
url: `?
method=user.getinfo&user=${userName}&api_key=${apiKey}&format=json`
});
getUserInfo().then(response => {
let data = response.data;
console.log(data.user.playcount); //logs second, displays correct
this.setState(state => ({
user: data
}));
});
}
render() {
console.log(this.state); //logs first and third, doesn't work on first but does on third
let toReturn;
if (this.state.user.length > 0) {
toReturn = <p>{this.state.user.user.playcount}</p>;
} else {
toReturn = <p>didn't work</p>;
}
return <div>{toReturn}</div>;
}
}
export default User;
React LifeCycle function sequence is Constructor and then it calls render method.
In constructor method it initialises the state which is currently empty user array.
Now it calls render() method as this.state.user is an empty array, referencing something out of it gives an error
this.state.user.user.playcount
this will generate an error if you dont have if condition.
After the first render it will call componentDidMount, now you fetch something update state. As setState occurred, render will be called again Now you have something in this.state.user then displaying will happen.
this.state.user.length > 0 is true
Look at this: https://reactjs.org/docs/react-component.html and https://reactjs.org/docs/conditional-rendering.html
You can right in single tag using conditional render like this
<p>{this.state.user.length ? this.state.user.user.playcount : 'loading'}
Hope this helps.
I think your problem might have something to do with the changing shape of the user value. You initialise the value to an empty array, but then—after the fetch is done—you assume it's an object (by using user.user).
Maybe you could simplify the code a bit to look more like the one below?
/* imports */
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
user: null // Make it explicit there's no value at the beginning.
};
}
componentDidMount() {
let getUserInfo = axios.create(/* ... */);
getUserInfo().then(response => {
let data = response.data;
this.setState({ // No need to for a setter function as you dno't rely on the previous state's value.
user: data.user // Assign the user object as the new value.
});
});
}
render() {
let toReturn;
// Since it's now a `null`, you can use a simple existence check.
if (this.state.user) {
// User is now an object, so you can safely refer to its properties.
toReturn = <p>{this.state.user.playcount}</p>;
} else {
toReturn = <p>No data yet.</p>;
}
return <div>{toReturn}</div>;
}
}
export default User;

React js, print out react component's state is not empty, but when call this.state, all data gone

In the React component's componentDidMount() I make an axios get request to receive a response and setState to the component. The response is correct, and when print out the component object in the component class with this, the object looks good. Then I call console.log(this.state), then every property of the component become empty. Why this happens? How can I get the state's property?
MyComponent.js
React component did mount method:
componentDidMount() {
getLastWeek(this); // here I make a get request
console.log('===');
console.log(this); // this line prints out an object will all the properties
console.log(this.state); // all properties of state disappear
}
The get request used above:
service.js
...
function getLastWeek(component) {
const lastWeek = getEndpoint(7);
Axios.get(lastWeek)
.then(res => {
const bpi = res.data.bpi;
const prices = Object.values(bpi);
component.setState({ lastWeek: prices });
})
.catch(err => {
console.log(err);
});
}
...
You are making an axios request which is an asynchronous function, so what is happening is you are using console.log(this.state) before the state gets set.
The render() method gets executed every time the state changes so if you put your console.log inside the render() method you should now see how your state change. Something like this:
class Example extends Component {
constructor() {
...
}
componentDidMount() {
...
}
render() {
console.log(this.state);
return(...);
}
}

Why does componentDidUpdate() create an infinite loop?

I've stored url and a token in state in Parent component. I'm passing an url and a token as props from parent Component to child Component. However, if there is some event in parent Component, setState() is triggered and as a result, componentDidUpdate() of child Component gets executed.
As componentDidUpdate() was causing an infinite loop (as it triggers setState() inside child component), I've placed condition. But this does not prevent the error.
Child Component ie DisplayRevenue is as follows:
import React, { Component } from 'react';
import '../App.css';
import ListData from './listdata.js'
var axios = require('axios');
class DisplayRevenue extends Component {
constructor(props){
super(props);
this.state = { data:[], url:"" }
console.log(this.props.url);
}
componentWillMount() {
this.loadRevenue(this.props.url, this.props.token);
}
componentDidUpdate(){ //creates infinite loop
// console.log(this.props.url);
this.loadRevenue(this.props.url, this.props.token);
}
setData(data){
//if(this.state.url != this.props.url){
if(this.state.data != data.data){
console.log(data.data); //(1)
// console.log(this.state.url); //(2)
this.setState(data:data);
console.log(this.state.data); //(3)
// console.log(this.props.url); //(4)
} //(1) & (3) yields exactly same value so does (2) & (4)
}
loadRevenue(url,token){
axios({
method:'get',
url:url,
headers: {
Authorization: `Bearer ${token}`,
},
})
.then( (response) => {
// console.log(response.data);
this.setData(response.data);
})
.catch(function (error) {
console.log("Error in loading Revenue "+error);
});
}
render() {
return (
<ListData data={this.state.data}/>
);
}
};
export default DisplayRevenue;
Parent Component ie MonthToDate is as below:
import React, { Component } from 'react';
import '../App.css';
import DisplayRevenue from './displayRevenue'
var axios = require('axios');
class MonthToDate extends Component {
constructor(props){
super(props);
this.state = {
data:null,
url:"http://localhost:3000/api/monthtodate"
}
//console.log(this.props.location.state.token);
}
groupBySelector(event){
if ((event.target.value)==="invoice"){
this.setState({url:"http://localhost:3000/api/monthtodate"})
} else if ((event.target.value)==="customer") {
this.setState({url:"http://localhost:3000/api/monthtodate?group-by=customerNumber"})
} else if ((event.target.value)==="month") {
this.setState({url:"http://localhost:3000/api/invoices?group-by=month"})
} else {
this.setState({url:"http://localhost:3000/api/monthtodate"})
}
console.log(this.state.url);
}
render() {
return (
<div>
<select onChange={(event)=>this.groupBySelector(event)}>
<option value="invoice">GROUP BY INVOICE</option>
<option value="customer">GROUP BY CUSTOMER</option>
<option value="month">GROUP BY MONTH</option>
</select>
<DisplayRevenue url={this.state.url} token={this.props.location.state.token}/>
</div>
);
}
}
export default MonthToDate;
What am I missing?
Also, after I've received the url in the child component I want to render different component based on that url. For example <ListData /> component can handle only one type of url. How can I render another component within render() based on the url type??
You are calling an ajax call in componentDidUpdate, and you set the state on the callback, that will trigger another call and update which will call the ajax request again and callback will set state again and so on.
Your condition in setData:
if(this.state.data != data.data)
will always return true as objects are reference type and can't be compared, no matter what data returned from the ajax call it will always be a different object and will return true in your condition.
Example:
var obj1 = {a:1}
var obj2 = {a:1}
console.log(obj1 != obj2); // returns true
What you can do, is compare primitives values inside the two objects.
For example:
if(this.state.data.id != data.id) // id could be a string or a number for example
EDIT
Another thing i forgot to mention which may not relate to your problem directly but should be enforced, Never do ajax requests inside componentWillMount or the constructor for that matter, as the render function will be invoked before your ajax request will finish. you can read about it in the DOCS.
Ajax requests should be invoked in componentDidMount life cycle method instead.
EDIT #2
Another thing that can be helpful, in the MonthToDate render function you are passing a new instance of a function on each render (which may cause a performance hit)
<select onChange={(event)=>this.groupBySelector(event)}>
Try changing it to this (the event will be passed automatically to the handler):
<select onChange={this.groupBySelector}>
You would also need to bind it in the constructor:
constructor(props){
super(props);
this.state = {
data:null,
url:"http://localhost:3000/api/monthtodate"
}
//console.log(this.props.location.state.token);
this.groupBySelector = this.groupBySelector.bind(this); // binds this to the class
}

How to update react es6 component after response from ajax call?

I am new to react and es6 and trying to create search field which fetch some data on type if user type minimum 3 chars on field its make ajax call using fetch api but I am not getting json data when I am running fetch snippet code in browser console its showing json data. whats wrong I am doing in my code. if I get data then how to populate search list I want to know what is the best way to update the component once received data. in below code I have created sub component where I have one prop called items I will update the prop through state is this right way to re-render the react component?
import React from 'react';
import SearchList from "./searchlist"
class SearchField extends React.Component {
constructor(props) {
super(props);
this.state = {SearchText:null,SearchData:{},KeyState:false, items:[]};
};
GetLocationData(){
fetch("http://jsonplaceholder.typicode.com/albums")
.then( (response) => {
return response.json() })
.then((json) => {
return json;
});
};
ChangeHandler(e){
e.preventDefault();
let isKeyUp = this.state.KeyState,
SearchFieldLength = e.target.value,
KeyLength = this.props.KeyRefresh;
if(!isKeyUp && SearchFieldLength.length > KeyLength){
let jsonData = this.GetLocationData();
//this.setState({SearchData:jsonData,KeyState:true})
console.log(jsonData);
}
};
componentDidMount(){
};
render() {
let PlaceholderText = this.props.PlaceHolderText;
return (
<div className="input-text-area">
<input type="text" placeholder={PlaceholderText} onChange={this.ChangeHandler.bind(this)} />
<SearchList items={this.state.items} />
</div>
);
}
}
export default SearchField;
use componentDidMount
componentDidMount {
this.GetLocationData();
}
You should use compnentDidMountand call the GetLocationData() from there. You will need to update your state in the GetLocationData() method which will then call the render method for you automatically.
GetLocationData(){
fetch("http://jsonplaceholder.typicode.com/albums")
.then( (response) => {
return response.json() })
.then((json) => {
this.setState({
items: json
})
return json;
});
};

Categories