Get API response to a function and populate controls - reactjs

I have created a react application where i am fetching an API and getting the response. below are the code,
export class EmpDetails extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.updateEmpName = this.updateEmpName.bind(this);
}
componentWillReceiveProps(nextProps) {
this.handleProp(nextProps);
if(nextProps){
this.GetData(nextProps);
} else {
console.log("Emp number not set");
}
}
GetData(props, EmpCollection) {
this.ApiCall(props);
}
ApiCall(props) {
$.ajax({
url: 'http://localhost:8081/getdata',
type: 'POST',
data: {Empnumber:props.Empnumber},
success: function(data) {
this.setState({EmpCollection: data.EmpCollection});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.Empnumber, status, err.toString());
}.bind(this)
});
}
getInitialState(){
return {
EmpCollection: []
}
}
updateEmpName(e) {
this.setState({EmpName: e.target.value});
}
render() {
return (
<form>
<div >
<input
type="text"
id="EmpName"
placeholder="Emp Name"
value={this.state.EmpName}
onChange={this.updateEmpName} />
</div>
</form>
);
}
}
I am able to get the response and can use it only in render(). and I wanted API response in GetData() or any other method so that i can set the state of there and populate controls. not in render. any idea how can i achieve this?

Well you need to save the response somewhere. I could be just variable outside of component or it could be component property. For example,
class YourComponent extends React.Component {
constructor() {
// your code ...
this.response = null;
}
callApi() {
const self = this;
$.ajax({
// your code ...
success: function(response) {
self.response = response;
}
})
}
someOtherMethod() {
console.log(this.response)
}
}

I would suggest you to make api call in life cycle method componentDidMount not in componentWillReceiveProps as recommended in react docs.
Need to change your api call method a little bit.
ApiCall(props) {
return $.ajax({
url: 'http://localhost:8081/getdata',
type: 'POST',
data: {Empnumber:props.Empnumber}
}).fail((responseData) => {
if (responseData.responseCode) {
console.error(responseData.responseCode);
}
});
}
Basically above call will return you a jquery promise which you can use later.
Now in what ever method you want to make ApiCall just use like this -
GetData(props,EmpCollection)
{
this.ApiCall(props)
.then(
function(data){
console.log(data);
// set the state here
},
function(error){
console.log(error);
}
);
}

Related

Unable to change state when using redirection with history.push

I got the following method:
export const getPublication = async (id, props) => {
const idJSON = {"id": id,}
return await axios({
method: 'post',
url: 'users/getPublicationByID',
data: idJSON
})
.then(function (response) {
return response.data
})
.catch(function (error) {
alert(error.response.status); // Works fine
props.history.push({
pathname: '/error',
state: { hasError: true, coding: error.response.status }
})
});
}
Now, this method seems to work fine as it redirects me from the page I was to '/error' when an error is caught. However, the error page doesn't seem to update its coding variable.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
coding: props.coding
};
}
render(){
const { t } = this.props;
var codeMsg = t('errors.errorCode') + this.state.coding;
alert(codeMsg); // says it's undefined!
return (
...
);
}
}
export default withTranslation()(ErrorBoundary);
The state provided to routed components is available at location.state
coding: props.location.state.coding
docs

React doesn't render data coming from an api response

I've seen a lot of questions and I couldn't get the solution
here is my code:
import React, { Component } from "react";
import axios from "axios";
import "./tree.css";
import "./mainTree";
class TablesTree extends Component {
constructor(props) {
super(props);
this.data = this.props.info;
this.state = {
fields: [],
data: [],
show: false
};
}
componentDidMount() {
var dataGet = [];
this.props.tables.forEach((name, i) => {
this.getFieldsTable(name.TABLE_NAME, (err, res) => {
if (res) {
dataGet.push({
TABLE_NAME: name.TABLE_NAME,
columns: res
});
}
});
});
this.setState({ data: dataGet });
}
getFieldsTable(table, callback) {
axios
.get(`table/columns?name=${this.data.user}&psw=${this.data.password}&schema=${this.data.schema}&table=${table}`)
.then(response => {
callback(null, response.data);
})
.catch(error => {
console.log(error);
});
}
render() {
return (
<div>
{this.state.data
? this.state.data.map((itm, i) => {
return (
<div>
<h1>{itm.TABLE_NAME}</h1>
</div>
);
})
: null}
</div>
);
}
}
export default TablesTree;
I've made console.log of the this.state.data
and the data is in there, but it doesn't renders anything
I've tried a lot of soutions, but I still without rendering the data, I will apreciate your help.
There's a few things I would change about your code, but most importantly you need to do this.setState after your push to dataGet (inside of your callback function).
Because your API call is asynchronous, you are only calling setState once when your component is initially mounted (and while dataGet is still empty).
getFieldsTable is asynchronous, so the dataGet array will be empty when you call setState.
You could return the promise from getFieldsTable and use Promise.all on all the promises, and use the data when all of them have resolved.
Example
class TablesTree extends Component {
// ...
componentDidMount() {
const promises = this.props.tables.map(name => {
return this.getFieldsTable(name.TABLE_NAME).then(res => {
return {
TABLE_NAME: name.TABLE_NAME,
columns: res
};
});
});
Promise.all(promises).then(data => {
this.setState({ data });
});
}
getFieldsTable(table) {
return axios
.get(`table/columns?name=${this.data.user}&psw=${this.data.password}&schema=${this.data.schema}&table=${table}`)
.then(response => {
return response.data;
})
.catch(error => {
console.log(error);
});
}
// ...
}

React cannot set property of undefined

I'm making a get request using axios. I know for a fact that when I make a get request, I get the correct data.
I have an array (allQuotes) in my constructor. However, when I try to reference it in componentDidMount, it's undefined.
class App extends Component {
constructor() {
super();
this.allQuotes = [];
}
componentDidMount() {
axios.get("http://getquote.herokuapp.com/get")
.then(function (response) {
this.allQuotes = response.data;
console.log(response.data);
this.getNewQuote();
})
.catch(function (error) {
console.log("Error: ", error);
//console.dir(error);
});
}
}
Upon running this, the console says "Cannot set property 'allQuotes' of undefined".
Why is this undefined?
It's better if you put allQuotes in state then you use setState
class App extends Component {
constructor() {
super();
this.state = {
allQuotes: [],
}
}
componentDidMount() {
axios.get("http://getquote.herokuapp.com/get")
.then(function (response) {
this.setState({ allQuotes: response.data })
console.log(response.data);
this.getNewQuote();
})
.catch(function (error) {
console.log("Error: ", error);
//console.dir(error);
});
}
You can use arrow functions to fix this. The problem is because if its another function, this refers to the function, and arrow function doesnt have one, instead it has the this of its referer.
axios.get("http://getquote.herokuapp.com/get")
.then((response)=>{
...
})
.catch( (error)=> {
...
});
Since you are using react, please make use of state.
What is a state ?
state are both plain JavaScript objects within the component and you can use setState to store the value within the component. You can refer https://reactjs.org/docs/faq-state.html
state = {
allQuotes: []
}
componentDidMount() {
axios.get("http://getquote.herokuapp.com/get")
.then(function (response) {
//this.allQuotes = response.data;
this.setState({
allQuotes: response.data
})
console.log(response.data);
this.getNewQuote();
})
.catch(function (error) {
console.log("Error: ", error);
//console.dir(error);
});
}

React lifecycle methods: fetch in componentDidMount

I'm trying to do a simple fetch through the componentDidMount lifecycle method. However, the result does not appear on the page as it should unless I have a one second timeout. I've gathered it's due to the async nature of the fetch, but how can I fix that without having to use setTimeout? Would componentDidUpdate work/how would you use it?
constructor(props) {
super(props);
this.state = { value: '' };
this.getValue= this.getValue.bind(this);
}
getValue() {
return (
fetch(url, {
method: 'GET',
}).then(response => {
if (response.status >= 400) {
throw new Error('no response: throw');
}
return response.json()
}).then(response => {
this.setState({value: response});
}).catch((error) => {
this.setState({
value: 'no response: catch'
})
})
);
}
componentDidMount(){
//this.getValue(); //does not work
setTimeout(() => this.getValue(), 1000); //this works & populates page
}
render() {
return (
<div>
<div>{this.state.value}</div>
</div>
)
}
Be sure you are binding your this.getValue method to the proper context in the constructor. When you put it in your setTimeout, you have it in a fat arrow function which binds to this implicitly.
constructor(props) {
super(props);
this.getValue = this.getValue.bind(this);
}
getValue() { ... }
componentDidMount() {
this.getValue();
}

How to make components/app wait for ajax call

I'm trying to find the best way to have my application and components rely on a single ajax call that I'm making. My app gets authenticated through a 3rd party and to really show any meaningful information to the user I have to use the info from their sign on and then call another service to get details about them. I've drawn from a couple of examples and so far have this
//auth.js
module.exports = {
login(cb) {
if (this.user) {
if (cb) cb(true)
this.onChange(true)
return;
}
//if we don't have info about the user we call the other service here
request((res) => {
if (res) {
this.user = res
if (cb) cb(true)
this.onChange(true)
} else {
if (cb) cb(false)
this.onChange(false)
}
})
},
getUser() {
return this.user
},
logout(cb) {
delete this.user
if (cb) cb()
this.onChange(false)
},
loggedIn() {
return !!this.user
},
onChange() {}
}
then in my components I'm doing this all over the place which just doesn't seem like a great pattern.
import React from 'react';
import ReactDOM from 'react-dom';
import auth from './auth'
export class ProductList extends React.Component{
constructor(props) {
super(props);
//subscribe to on change event from the auth class
auth.onChange = this.updateAuth.bind(this)
this.state = {results: []};
}
componentWillMount() {
//call login. if already logged in this method just returns the current user
auth.login();
}
getProducts() {
if(this.state.loggedIn) {
$.get(config.server.url + "/api/User/" + auth.getUser().Id + "/Products", function(result) {
this.setState({
results: result.data.products
});
}.bind(this));
}
}
updateAuth(loggedIn) {
this.setState({
loggedIn: loggedIn
});
this.getProducts()
}
componentDidMount() {
this.getProducts()
}
render() {
return (
<table>
<tbody>
{this.state.results.map(function(result) {
return <ProductItem key={result.Id} data={result}/>;
})}
</tbody>
</table>
)
}
};
ReactDOM.render(
(<ProductList/>),
document.getElementById('react-forms')
);
So I basically just hook up an event handler in every single react component I have and check the same properties all over the place and it just seems fragile. I guess I'm looking for a way to tell me 'App' that I'm waiting for something to happen first before my components are valid.
I suggest you follow the structure outlined in the react tutorial (https://facebook.github.io/react/docs/tutorial.html#fetching-from-the-server). The ajax call is made from the top-level component CommentBox using the jquery ajax function, and then passed down to other components CommentList and CommentForm via props. The code below is taken directly from the tutorial. The syntax is slightly different since you are using es6, but the concepts remain the same.
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadCommentsFromServer();
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm />
</div>
);
}
});

Resources