How to make components/app wait for ajax call - reactjs

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

Related

React 0.13 get data from REST endpoint

Working with older React.
How to correctly get data from an endpoint?
It complains about state
I am a React newbie. Looked on Tutorials but it seems developer who did this code used some other convention in creating Classes/Components.
Tried this:
import React from "react"
import Utils from "utils"
import {A, makeUrl, redirectTo} from "routing"
import Settings from "settings"
import ProjectApi from "api/project"
import FlashMessagesService from "flash_messages"
var ProjectHeader = React.createClass({
displayName: 'ProjectHeader',
state = {
project: [],
},
componentDidMount() {
// need to make the initial call to getData() to populate
// data right away
// Now we need to make it run at a specified interval
setInterval(this.refresh, 1000); // runs every 5 seconds.
},
refresh : function(){
var props = this.props
var providers = Settings.providers['project.header.info'] || []
providers
.filter(function(provider) {
return provider.component.isApplicable(props.project)
})
.forEach(function (provider) {
projectInfo.push(<provider.component project={props.project} {...props}/>)
})
fetch('/api/v1/project/'+props.project.pk)
.then(response => response.json())
.then(data => {
this.setState({ project: data.project });
});
if (this.state.project.analysis_status == 'succeeded') {
window.location.reload();
}
},
Thanks,
There was a typo in the state line (line 11) which should have been state: { rather than state = {. Another potential complaint it might have had is that during runtime after the component was unmounted, the interval would've continued firing, so I added a clearInterval in componentWillUnmount.
import Utils from 'utils';
import { A, makeUrl, redirectTo } from 'routing';
import Settings from 'settings';
import ProjectApi from 'api/project';
import FlashMessagesService from 'flash_messages';
var ProjectHeader = React.createClass({
displayName: 'ProjectHeader',
intervalId: null,
state: {
project: [],
},
componentDidMount() {
// need to make the initial call to getData() to populate
// data right away
// Now we need to make it run at a specified interval
this.intervalId = setInterval(this.refresh, 1000); // runs every 5 seconds.
},
componentWillUnmount(){
clearInterval(this.intervalId);
},
refresh: function () {
var props = this.props;
var providers = Settings.providers['project.header.info'] || [];
providers
.filter(function (provider) {
return provider.component.isApplicable(props.project);
})
.forEach(function (provider) {
projectInfo.push(
<provider.component project={props.project} {...props} />
);
});
fetch('/api/v1/project/' + props.project.pk)
.then((response) => response.json())
.then((data) => {
this.setState({ project: data.project });
});
console.log(hits);
if (hits.analysis_status == 'succeeded') {
window.location.reload();
}
},

How to setState after getting data from Firestore

I am currently able to get user data from the Firestore however I'm having trouble saving the users document data. I'm getting an error below in my console
TypeError: this.setState is not a function
at Object.next (RepRequest.js:32)
at index.cjs.js:1344
at index.cjs.js:1464
I attempted to follow another user's question from
Can't setState Firestore data, however still no success.
I do have a two api request right after getting the data and I am able to setState then. I tried incorporating the Firestore request in the promise.all but was unable to successfully, which is why I have it separated. Maybe I'm headed down the wrong path, any guidance is appreciated.
import React, { useEffect, useState } from "react";
import app from "./config/base.js";
import axios from "axios";
export default class RepRequest extends React.Component {
constructor(props) {
super(props);
this.state = {
userInfo: [],
fedSens: [],
fedReps: []
};
}
componentDidMount() {
const items = [];
app.auth().onAuthStateChanged(function(user) {
if (user) {
console.log("User is signed in");
let db = app
.firestore()
.collection("user")
.doc(user.uid);
db.get().then(doc => {
if (doc.exists) {
console.log("Document data:", doc.data());
items.push(doc.data());
} else {
console.log("No doc exists");
}
});
}
this.setState({ userInfo: items });
});
Promise.all([
axios.get(
`https://api.propublica.org/congress/v1/116/senate/members.json`,
{
headers: { "X-API-Key": "9wGKmWl3kNiiSqesJf74uGl0PtStbcP2mEzSvjxv" }
}
),
axios.get(
`https://api.propublica.org/congress/v1/116/house/members.json`,
{
headers: { "X-API-Key": "9wGKmWl3kNiiSqesJf74uGl0PtStbcP2mEzSvjxv" }
}
)
]).then(([rest1, rest2]) => {
this.setState({
fedSens: rest1,
fedReps: rest2
});
});
}
render() {
if (this.state.fedReps.length <= 0)
return (
<div>
<span>Loading...</span>
</div>
);
else {
console.log(this.state.fedReps);
return <div>test</div>;
}
}
}
Your problem arises from mixing lambda function declarations ((...) => { ... }) and traditional function declarations (function (...) { }).
A lambda function will inherit this from where it was defined but a traditional function's this will be isolated from the context of where it was defined. This is why it is common to see var self = this; in legacy-compatible code because this usually didn't match what you wanted it to.
Here is an example snippet demonstrating this behaviour:
function doSomething() {
var anon = function () {
console.log(this); // 'this' is independent of doSomething()
}
var lambda = () => {
console.log(this); // inherits doSomething's 'this'
}
lambda(); // logs the string "hello"
anon(); // logs the 'window' object
}
doSomething.call('hello')
Solution
So you have two approaches available. Use whichever you are comfortable with.
Option 1: Use a lambda expression
app.auth().onAuthStateChanged(function(user) {
to
app.auth().onAuthStateChanged((user) => {
Option 2: Assign a "self" variable
const items = [];
app.auth().onAuthStateChanged(function(user) {
// ...
this.setState({ userInfo: items });
}
to
const items = [];
const component = this; // using "component" makes more sense than "self" in this context
app.auth().onAuthStateChanged(function(user) {
// ...
component.setState({ userInfo: items });
}

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

Get API response to a function and populate controls

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

react-router and flux how to?

I try to interact with data on route changes(I use react-router 1.0.0) but inherited components are not fetching data from API
basically I have my base route which is interacting with the store but the inherited route like /:slug does not updates the component with new data
it seems like the _onChange will not fire up!
the component
import React from 'react';
import PropertyStore from '../stores/PropertyStore';
import AppActions from '../actions/AppActions'
class Property extends React.Component{
constructor(){
super();
this.state = {
property: []
}
this._onChange = this._onChange.bind(this);
this.handleChange = this.handleChange.bind(this);
}
componentWillMount(){
PropertyStore.addChangeListener(this._onChange);
}
componentWillUnmount() {
PropertyStore.removeChangeListener(this._onChange);
}
componentDidMount () {
AppActions.getProperty( this.props.params.slug );
}
handleChange(e) {}
_onChange() {
this.setState({
property: PropertyStore.getProperty( this.props.params.slug )
});
}
render() {
var property;
if (this.state.property) {
console.log(this.state.property) //empty!!
property = this.state.property.map(function (detail, i) {
return (
<div>{detail.description}</div>)
});
}
return(
<div className="Property"> {property} </div>
)
}
}
module.exports = Property;
AppActions
'use strict';
var AppDispatcher = require('../dispatchers/AppDispatcher');
var AppConstants = require('../constants/AppConstants');
var PropertiesStore = require('../stores/PropertiesStore');
var PropertyStore = require('../stores/PropertyStore');
var Promise = require('es6-promise').Promise; // jshint ignore:line
var Api = require('../services/Api');
var AppActions = {
getProperties: function () {
Api
.get('/api/v2/properties')
.then(function (properties) {
AppDispatcher.handleViewAction({
actionType: AppConstants.RECEIVE_PROPERTIES,
properties: properties.data.properties
});
})
.catch(function () {
AppDispatcher.handleViewAction({
actionType: AppConstants.RECEIVE_ERROR,
error: 'There was a problem getting the workshops'
});
});
},
getProperty: function (slug) {
Api
.get('/api/v2/properties/'+ slug)
.then(function (property) {
AppDispatcher.handleViewAction({
actionType: AppConstants.RECEIVE_PROPERTY,
property: property.data.property
});
})
.catch(function () {
AppDispatcher.handleViewAction({
actionType: AppConstants.RECEIVE_ERROR,
error: 'There was a problem getting the Voucher'
});
});
},
getMedia: function () {
Api
.get('/wp-json/wp/v2/workshops')
.then(function (workshops) {
AppDispatcher.handleViewAction({
actionType: WorkshopConstants.RECEIVE_MEDIA,
workshops: workshops
});
})
.catch(function () {
AppDispatcher.handleViewAction({
actionType: WorkshopConstants.RECEIVE_ERROR,
error: 'There was a problem getting the categories'
});
});
}
};
module.exports = AppActions;

Resources