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;
Related
I am using a class component with react and this error popped up. Wondering, did anyone use a this inside axios before or know how to? Thank you in advance
type State = {
fetchedPassword: string;
fetchedPassword: string;
}
type Props = {
}
export default class MyComponent extends React.Component<Props,State>() {
constructor(props: Props) {
super(props);
this.state= {
fetchedPassword: "",
fetchedUsername: ""
}
}
authLogin = (e:any) => {
e.preventDefault();
const { fetchedUsername, fetchedPassword } = this.state;
axios
.get(url)
.then(function (response) {
this.setState({ fetchedPassword: response.data.password }); //error appears here
this.setState({ fetchedUsername: response.data.username }); //and here
})
.catch(function (error) {
console.log("Error: " + error);
});
}
}
The error says
'this' implicitly has type 'any' because it does not have a type annotation.ts(2683)
MyComponenet.tsx(26, 13): An outer value of 'this' is shadowed by this container.
I'm not sure how to solve this
Make a copy of you component instance this and store it in that constant:
authLogin = (e:any) => {
e.preventDefault();
const { fetchedUsername, fetchedPassword } = this.state;
const that = this
axios
.get(url)
.then(function (response) {
that.setState({ fetchedPassword: response.data.password });
that.setState({ fetchedUsername: response.data.username });
})
.catch(function (error) {
console.log("Error: " + error);
});
}
Move authLogin outside your constructor, then, as a last line in your constructor, add
this.authLogin = this.authLogin.bind(this);
See https://www.freecodecamp.org/news/react-binding-patterns-5-approaches-for-handling-this-92c651b5af56/ for more info.
I am trying to make a movie search app with React and have made an API call to The Movie Database API. What I am trying to do is get the data of the new movie releases, but then make another API call to get the specific details for each of those new releases since that data is stored in a different location.
I am able to access the data from the first API call, but when I try to access the movie taglines from the second data object, the console outputs "Cannot read property 'tagline' of undefined".
App.js
class App extends Component {
constructor(props) {
super(props)
this.state = {
movieRows: [],
ids: [],
movieDetails: [],
}
this.performSearch = this.performSearch.bind(this);
}
componentDidMount() {
this.performSearch();
}
performSearch() {
const urlString = "https://api.themoviedb.org/3/movie/popular?api_key=6db3cd67e35336927891a72c05&language=en-US&page=1";
axios.get(urlString)
.then(res => {
const results = res.data.results
let movieRows = [];
let movieDetails = [];
results.forEach((movie) => {
movieRows.push(movie);
axios.get(`https://api.themoviedb.org/3/movie/${movie.id}?api_key=6db3cd67e35336927891a72c05&language=en-US`)
.then(res => {
movieDetails.push(res.data);
})
.catch(function (error) {
console.log(error);
});
});
this.setState({
movieRows: movieRows,
movieDetails: movieDetails,
});
})
.catch(function (error) {
console.log(error);
});
}
Content.js
export default class Content extends Component {
constructor(props) {
super(props)
this.state = {
name: 'Jonathan',
}
this.filmLoop = this.filmLoop.bind(this);
}
filmLoop() {
let movieData = this.props.globalState.movieRows;
let movieDetails = this.props.globalState.movieDetails;
return movieData.map((movie, index) => {
return (
<div className="film" key={index}>
<img className="poster" src={`http://image.tmdb.org/t/p/w342${movie.poster_path}`} alt="The Dark Knight poster" />
<div className="film-info">
<div className="film-title">
<h3>{movie.title}</h3>
</div>
<h4>{movieDetails[index].tagline}</h4>
*I get the error from the last line
Well the main issue is that you are calling setState outside your .then you have to update the state inside your then or your catch. This is because the promise is an async function, so you have to change the state only when the promise has been resolved of rejected.
performSearch() {
const urlString = "https://api.themoviedb.org/3/movie/popular?api_key=6db3cd67e35336927891a72c05&language=en-US&page=1";
axios.get(urlString)
.then(responsePopular => {
const results = responsePopular.data.results
let movieRows = [];
let movieDetails = [];
results.forEach((movie) => {
movieRows = [...movieRows, movie];
axios.get(`https://api.themoviedb.org/3/movie/${movie.id}?api_key=6db3cd67e35336927891a72c05&language=en-US`)
.then(responseMovie => {
movieDetails = [...movieDetails, responseMovie.data];
this.setState({
movieRows: movieRows,
movieDetails: movieDetails,
})
})
.catch(function (error) {
console.log(error);
});
});
})
.catch(function (error) {
console.log(error);
});
}
I think that this could solve your issue.
Array not passing from service to component:
In the test() function on the service.ts page, google calendar data is successfully being read and pushed to an array called response. All the data logs.
When lesson-summary.component.ts calls on the test() function, the response array data does not show up in the lesson-summary.component.html
Thanks for any help!
google-calendar.service.ts
import { Injectable, Directive } from "#angular/core";
import * as moment from "moment-timezone";
declare var gapi: any;
#Injectable({
providedIn: "root"
})
export class GoogleCalendarService {
private response = [];
constructor() { }
test() {
gapi.load("client", () => {
gapi.client.init({
apiKey: "API string here",
discoveryDocs: ["https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest"]
}).then(() => {
var month = moment().month();
const firstOfMonth = moment().startOf("month").format("YYYY-MM-DD hh:mm");
const lastOfMonth = moment().endOf("month").format("YYYY-MM-DD hh:mm");
var firstOfMonthUTC = moment.tz(firstOfMonth, "America/Toronto").format();
var lastOfMonthUTC = moment.tz(lastOfMonth, "America/Toronto").format();
return gapi.client.calendar.events.list({
calendarId: "calendar id here",
timeMax: lastOfMonthUTC,
timeMin: firstOfMonthUTC,
singleEvents: true
});
})//end of .then
.then((data) => {
this.response.push.apply(this.response, data.result.items);
console.log(data.result.items, "data.result.items");
return this.response;
});//end of .then
});//end of .load
}//end of test
}//end of export
lesson-summary.component.ts
import { Component, OnInit } from "#angular/core";
import { Observable } from "rxjs";
import { GoogleCalendarService } from "../google-calendar.service";
declare var gapi: any;
#Component({
selector: "app-lesson-summary",
templateUrl: "./lesson-summary.component.html",
styleUrls: ["./lesson-summary.component.css"]
})
export class LessonSummaryComponent implements OnInit {
private response;
constructor(
private calendarService: GoogleCalendarService) {
this.response = this.calendarService.test();
}
ngOnInit() {
}
}
lesson-summary.component.html
<ul>
<li *ngFor = "let item of response">
{{ item.summary }}
</li>
</ul>
That's because you're mixing promises and sync functions in an incorrect way, so the test() function will not return anything.
Try adding a promise to your test():
test() {
return new Promise(resolve => { // <-- now test func return promise
gapi.load("client", () => {
gapi.client.init({
apiKey: "API string here",
discoveryDocs: ["https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest"]
}).then(() => {
// code...
}).then((data) => {
// code...
resolve(this.response); // <-- when we have the response, we are resolving the promise
});
});
});
}
And then use this promise in the component:
this.calendarService.test().then(data => this.response = data);
Learn more about promises on MDN
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);
});
}
// ...
}
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>
);
}
});