React + axios async call - reactjs

I am using axios to fetch some data on the componentDidMount lifecycle method. The call was originally in the same file and thus i could update this.setState({members}). however I wanted to abstract the logic into a serperate file to keep the code clean.
I split the logic into a new file and start using the axios async await pattern. hoewever it seems like React doesn't wait for my axios call to finish. I went throught the docs and several posts but I cannot seem to find the problem. any hints are appreciated!
PS: I used create react app as base and added the dns mock:
https://github.com/hapijs/isemail/issues/26
Teamcard file
import React from "react";
import { Icon } from "office-ui-fabric-react/lib/Icon";
import { PrimaryButton } from "office-ui-fabric-react/lib/Button";
import TeamCardLogo from "./teamCardLogo/teamCardLogo";
import TeamCardPersona from "./teamCardPersona/teamCardPersona";
import { GetGroupMembers } from "../../HttpRepositories/graphRepository";
class TeamCard extends React.Component {
state = {
members: ""
};
componentDidMount() {
let members = GetGroupMembers(this.props.id, 5);
console.log("members", members);
this.setState({ members });
}
render() {
let members = "";
if (
typeof this.state.members !== "undefined" &&
this.state.members.length > 0
) {
members = this.state.members.map((member, i) => {
return (
<div className="team-card-body__personas-wrapper-person" key={i}>
<TeamCardPersona
className="team-card-body__personas-wrapper-person"
member={member}
key={i}
/>
</div>
);
});
}
let favouriteIcon = "";
this.props.isFavorite === true
? (favouriteIcon = <Icon iconName="FavoriteStarFill" />)
: (favouriteIcon = <Icon iconName="FavoriteStar" />);
return (
<article className="team-card-wrapper">
<header className="team-card-wrapper__header">
<TeamCardLogo
className="team-card-wrapper__header-photo"
teamId={this.props.id}
/>
<div className="team-card-wrapper__header-options-wrapper">
<div className="header-options__icon-group">
<div className="header-options__group-type">
<Icon iconName="LockSolid" />
</div>
</div>
<div className="header-options__icon-group">
<div className="header-options__favourite">{favouriteIcon}</div>
</div>
</div>
</header>
<section className="team-card-body">
<h1>{this.props.title}</h1>
<h2>Leden: {this.props.memberCount}</h2>
<div className="team-card-body__personas-wrapper">{members}</div>
<p className="description">{this.props.description}</p>
<div className="team-card-body__join-button-wrapper">
<PrimaryButton text="Lid worden" />
</div>
</section>
</article>
);
}
}
export default TeamCard;
seperated get request file:
import { getGraphToken } from "../adalConfig";
import axios from "axios";
import { resolve } from "dns";
export async function GetGroupMembers(groupId, numberOfMembers) {
// we initiate a new token, to be sure that it didn't expire.
let graphToken = getGraphToken();
let response = await axios({
url: `https://graph.microsoft.com/v1.0/groups/${groupId}/members?$top=${numberOfMembers}`,
method: "GET",
headers: { Authorization: "Bearer " + graphToken }
});
if (response.status != 200 && response.status != 204) {
console.log("error");
}
console.log("returning data", response.data.value);
return response.data.value;
}
Screenshot of logging:

You are missing an await here: let members = await GetGroupMembers(this.props.id, 5); and componentDidMountmust be declared async.

Related

ternary operator issue in React return

I'm trying to use a return with a ternary. But I have an error saying Unexpected token, expected "," (31:21) on the dot of Object.keys but I don't know what is it. I would like some help to figure it out, please.
import React, { Component } from 'react';
import SpotifyLogin from 'react-spotify-login';
import axios from 'axios';
import '../style.css';
import SelectWidget from './SelectWidget';
class SpotLogin extends Component {
state = {
authData: {}
}
stockInfo = (e) => {
this.setState({
authData: e
});
}
getUserInfo() {
axios('https://api.spotify.com/v1/me', {
method: 'GET',
headers: {'Authorization' : 'Bearer ' + this.state.authData.access_token}
})
.then(data => {
console.log(data.data);
})
}
render() {
return(
{ Object.keys(this.state.authData).length === 0 ? (
<div>
<SpotifyLogin clientId = MY_ID
redirectUri = 'http://localhost:3000/callback'
onSuccess={this.stockInfo}
buttonText= "Spotify"
className= "btn-spotify"
/>
</div>
) :
<div>
<SelectWidget />
</div>
}
)
}
}
export default SpotLogin;
You only need curly braces inside JSX. The Object.keys statement isn't technically inside the JSX, it's just being directly returned. So try removing the curly braces around it:
render() {
return(
Object.keys(this.state.authData).length === 0 ? (
<div>
<SpotifyLogin clientId = MY_ID
redirectUri = 'http://localhost:3000/callback'
onSuccess={this.stockInfo}
buttonText= "Spotify"
className= "btn-spotify"
/>
</div>
) :
<div>
<SelectWidget />
</div>
)
}

Show pull request list with Github API from a specific repository

I'm remaking my previous question. I'm working on a project that shows a repository list from github api.
It's working fine, I get all the repo info from the api but I need that when I click in one button from a particular repository, it shows the pull request info from https://api.github.com/repos/:USER:/:REPONAME:/pulls in the modal i have made.
But i have no idea how to do it. Can someone help me?
import React, { Component } from "react";
import axios from "axios";
import Navbar from "./components/Navbar";
import Modal from "react-modal";
class App extends Component {
constructor() {
super();
this.state = {
githubData: [],
isActive: false
};
}
componentDidMount() {
axios
.get(
"https://api.github.com/search/repositories?q=language:Java&sort=stars&page=1"
)
.then((res) => {
console.log("res", res);
this.setState({ githubData: res.data.items });
});
Modal.setAppElement("body");
}
toggleModal = () => {
this.setState({
isActive: !this.state.isActive
});
};
render() {
const { githubData } = this.state;
return (
<div className="container">
<Navbar />
<div className="row">
{githubData.map((name, index) => (
<div className="col-md-12" key={name.id}>
<img src={name.owner.avatar_url} alt="Imagem do projeto" />
<h1>
Projeto:
{name.name}
</h1>
<h1>
Autor:
{name.owner.login}
</h1>
<h1>
Descrição:
{name.description}
</h1>
<h1>
Link:
<a href={name.homepage}>{name.homepage}</a>
</h1>
<h1>
Stars:
{name.stargazers_count}
</h1>
<button onClick={this.toggleModal}>
Open pull request for this repository
</button>
<Modal
isOpen={this.state.isActive}
onRequestClose={this.toggleModal}
>
<p onClick={this.toggleModal}>
PULL REQUEST LIST FROM THIS REPOSITORY
</p>
</Modal>
</div>
))}
</div>
</div>
);
}
}
export default App;
The modal is working, i just need to get the pull request info from the api but no idea how to get from a specific repository.
After sometime i made it work.
I just created a new component, using axios to call the github api and passing as props the user and repo.
Heres the code:
import React from "react";
import axios from "axios";
const PullRequest = (props) => {
axios
.get(
"https://api.github.com/repos/" + props.user + "/" + props.repo + "/pulls"
)
.then((repo) => {
console.log("repo", repo);
});
return (
<div>
<p>Nome do repositório: {props.repo}</p>
<p>Nome do usuário: {props.user}</p>
</div>
);
};
export default PullRequest;
Cheers :)
Checkout my GitHub Open Source project, which I do just that and more using a Node.js REST server:
https://github.com/ConfusedDeer/Git-Captain
Here is an example of how one would do with Node.js (see GitHub Project for full example):
else if ((appName === 'gitCaptain') && (webServ === 'searchForPR')) //create Branches
{
var urlForPRsearch = gitHubAPIendpoint + "/repos/" + orgName + "/" + req.body.repo + "/pulls?" + "state=" + req.body.state + "&base=" + req.body.prBaseBranch + "&" + req.body.token;
// noinspection JSDuplicatedDeclaration
var options = {
method: 'GET',
url: urlForPRsearch,
headers: {
'User-Agent': 'request'
}
};
function callbackForPRSearch(error, response) {
if (!error && response.statusCode === 200) {
res.send(response);
}
else {
res.send(response); //send back response if not 200, but needs to be updated to handle exception.
}
}
request(options, callbackForPRSearch);
}

React this.props.id is undefined in part of class

In my React application I need the userId in the Timeline class to get the posts from a user, but React says that it's undefined.
If I say in the rendered part
{ this.props.id }
Than it will show the right id..
I already tried every solution that I could possibly find on the internet.
import React, { Component } from 'react'
import axios from 'axios'
import Timeline from './Timeline'
class Profile extends Component{
state = {
user: {}
}
componentDidMount() {
axios.get(`http://localhost:8090/user/${this.props.match.params.id}`)
.then(res =>{
const user = res.data
this.setState({ user: user })
})
}
render(){
return(
<div>
<h1>This is the profile page of { this.state.user.username }.</h1>
<img src={this.state.user.profilePicture} ></img>
<h3> E-mailaddress: { this.state.user.mail }</h3>
<Timeline id={this.state.user.id}/>
</div>
)}
}
export default Profile
import Cookies from 'universal-cookie'
import React, { Component } from 'react'
import axios from 'axios'
const cookies = new Cookies()
class Timeline extends Component {
state = {
user: cookies.get('user'),
posts: []
}
componentDidMount() {
const id = this.props.id
console.log("ID IS " + id)
if (this.state.user === undefined)
return
axios.get(`http://localhost:8090/user/${id}/postEntities`)
.then(response => {
this.setState({
posts: response.data._embedded.post
})
})
.catch(error => {
console.log(error)
})
}
render() {
if (this.state.user !== undefined) {
if (this.state.posts.length <= 0) {
return (
<main>
<h2>Personal timeline</h2>
<h2>This id works: { this.props.id }</h2>
<h6>There does not seem to be anything here..<br />Create a post and come back later!</h6>
</main>
)
} else {
return (
<main>
<h2>Personal timeline</h2>
{
this.state.posts.map(post => {
return (
<div>
<h5>{ post.title }</h5>
<img src={post.pictureUrl} width="200" height="200"></img>
<p><i>You took this picture at { post.longitude }, { post.latitude }</i></p>
</div>
)
})
}
</main>
)
}
}
else {
return (
<h5>You need to be logged in to use this feature</h5>
)
}
}
}
export default Timeline
The expected output in the url needs to be 2 but is undefined, the expected value in the rendered part is 2 and it outputs 2.
With react, the componentDidMount of children is called BEFORE the one from the parent.
So, when the componentDidMount of Timeline is called the first time, the componentDidMount of Profile has not been called, so there is no userId yet.
To avoid this problem, you should render the Timeline only when the Profile component has been mounted and when you have your user id.
So something like that in the render of Profile
render(){
return(
<div>
<h1>This is the profile page of { this.state.user.username }.</h1>
<img src={this.state.user.profilePicture} ></img>
<h3> E-mailaddress: { this.state.user.mail }</h3>
{this.state.user.id && (
<Timeline id={this.state.user.id}/>
)}
</div>
)}
Because
this.state.user.id
only has value when function axios.get in componentDidMount has done. while function render() is called before.
So, To avoid undefined, you must set state with format:
state = {
user: {id : 0} //or null
}
Initially you won't have user.id, it is coming from axios service call. In this case wait till you get response and then show timeline based on condition in render.
import React, { Component } from 'react'
import axios from 'axios'
import Timeline from './Timeline'
class Profile extends Component{
state = {
user: {}
}
componentDidMount() {
axios.get(`http://localhost:8090/user/${this.props.match.params.id}`)
.then(res =>{
const user = res.data
this.setState({ user: user })
})
}
render(){
return(
<div>
<h1>This is the profile page of { this.state.user.username }.</h1>
<img src={this.state.user.profilePicture} ></img>
<h3> E-mailaddress: { this.state.user.mail }</h3>
{typeof(this.state.user.id) !== 'undefined' ? <Timeline id={this.state.user.id}/> : ''}
</div>
)}
}
export default Profile
What variable is undefined? this.state.user.id?
If so, that probably means that you start with user: {}, then you make a promise and then set the state. The problem is that a promise will take time to fulfill, so meanwhile you are still with user: {} and this.state.user.id gives undefined.
When you call <Timeline id={this.state.user.id}/> make sure you have a id and email in your state. Or define your state with user: {is: '', email:''} or do a conditional render. Hope I understood your problem correctly!

React componentDid mount get Async image. if not done before changing page, how to cancel the request?

I am kinda new to react coming from an Angular background. recently I am building a page where I display cards that all need to fetch an image from the microsoft graph api. The return type of the call is a base64 string. I don't want to put this into redux because it will make my devtools unreadable.
Therefore I decided to make a async call in the componentDidMount lifecycle hook with the this.isMounted pattern (Cancel All Subscriptions and Asyncs in the componentWillUnmount Method, how?). The problem however is that for some reason this doesn't unsubscribe the async call that I made. I am not sure whether I made a mistake or whether it needs to be unsubscribed instead of checking whether the component is mounted. but I cannot find any information on how to deal with this.
Any help would be appreciated.
My teamCard code:
import React from "react";
import { Icon } from "office-ui-fabric-react/lib/Icon";
import TeamCardLogo from "./teamCardLogo/teamCardLogo";
import TeamCardPersona from "./teamCardPersona/teamCardPersona";
import { GetGroupMembers } from "../../HttpRepositories/graphRepository";
import { FormattedMessage } from "react-intl";
import Fade from "react-reveal/Fade";
import { Modal } from "office-ui-fabric-react/lib/Modal";
import reactAppInsights from "react-appinsights";
import TeamModal from "./teamModal/teamModal";
class TeamCard extends React.Component {
state = {
members: "",
modelIsOpen: false
};
async componentDidMount() {
let members = await GetGroupMembers(this.props.id);
if (this.state.member !== "error") {
this.setState({ members });
}
}
_openModal = id => {
this.setState({ modelIsOpen: true });
};
_closeModal = () => {
this.setState({ modelIsOpen: false });
};
render() {
let members = "";
if (
typeof this.state.members !== "undefined" &&
this.state.members.length > 0 &&
this.state.members !== "error"
) {
members = this.state.members.map((member, i) => {
if (i < 5) {
return (
<div className="team-card-body__personas-wrapper-person" key={i}>
<TeamCardPersona
className="team-card-body__personas-wrapper-person"
member={member}
key={i}
/>
</div>
);
}
});
} else {
members = <div className="no-members-spacer" />;
}
let favouriteIcon = "";
this.props.isFavorite === true
? (favouriteIcon = <Icon iconName="FavoriteStarFill" />)
: (favouriteIcon = <Icon iconName="FavoriteStar" />);
return (
<React.Fragment>
{/* <Fade bottom delay={this.props.delay} appear={true}> */}
<article
className="team-card-wrapper"
onClick={() => this._openModal(this.props.id)}
>
<header className="team-card-wrapper__header">
<TeamCardLogo
injectClass="team-card-wrapper__header-photo"
teamId={this.props.id}
/>
<div className="team-card-wrapper__header-options-wrapper">
<div className="header-options__icon-group">
<div className="header-options__group-type">
<Icon iconName="LockSolid" />
</div>
</div>
<div className="header-options__icon-group">
<div className="header-options__favourite">{favouriteIcon}</div>
</div>
</div>
</header>
<section className="team-card-body">
<h1>{this.props.title}</h1>
<h2>
{" "}
<FormattedMessage
id="teamcard.memberCount"
defaultMessage="Leden"
/>
:{this.state.members.length}
</h2>
<div className="team-card-body__personas-wrapper">{members}</div>
<p className="description">{this.props.description}</p>
{/* <div className="team-card-body__join-button-wrapper">
<PrimaryButton text="Lid worden" />
</div> */}
</section>
</article>
{/* </Fade> */}
<Modal
titleAriaId={this._titleId}
subtitleAriaId={this._subtitleId}
isOpen={this.state.modelIsOpen}
onDismiss={this._closeModal}
isBlocking={false}
containerClassName="team-modal-wrapper"
>
<TeamModal
teamId={this.props.id}
title={this.props.title}
description={this.props.description}
favorite={this.props.isFavorite}
members={this.state.members}
closeModal={this._closeModal}
/>
</Modal>
</React.Fragment>
);
}
}
export default TeamCard;
my TeamCardLogo code (makes the async call)
import React from "react";
import { Icon } from "office-ui-fabric-react/lib/Icon";
import { getImage } from "../../../HttpRepositories/graphRepository";
class TeamCardImage extends React.Component {
constructor(props) {
super(props);
this._isMounted = false;
}
state = {
groupImage: ""
};
getLogo = () => {};
async componentDidMount() {
this._isMounted = true;
if (this._isMounted) {
let logo = await getImage(
`https://graph.microsoft.com/v1.0/groups/${
this.props.teamId
}/photo/$value`
);
if (logo !== "error") {
this.setState({ groupImage: logo });
}
}
}
render() {
let injectedClassName =
this.props.injectClass != "" ? this.props.injectClass : "";
let headerPhoto = "";
const groupIcon = (
<div className="team-card-wrapper__header-photo-alt">
<Icon iconName="Group" />
</div>
);
if (this.state.groupImage === "") {
headerPhoto = groupIcon;
} else {
headerPhoto = <img src={this.state.groupImage} alt=" " />;
}
return (
<React.Fragment>
<div className={injectedClassName}>{headerPhoto}</div>
</React.Fragment>
);
}
componentWillUnmount() {
this._isMounted = false;
}
}
export default TeamCardImage;
my httpRepos code
import { getGraphToken } from "../adalConfig";
import axios from "axios";
export const GetGroupMembers = async groupId => {
// we initiate a new token, to be sure that it didn't expire.
let graphToken = getGraphToken();
try {
let response = await axios({
url: `https://graph.microsoft.com/v1.0/groups/${groupId}/members?$select=id,displayName`,
method: "GET",
headers: { Authorization: "Bearer " + graphToken }
});
if (response.status != 200 && response.status != 204) {
return "error";
}
return await response.data.value;
} catch (error) {
return "error";
}
};
export const getImage = async url => {
// we initiate a new token, to be sure that it didn't expire.
let graphToken = getGraphToken();
try {
let response = await axios({
url: url,
method: "get",
responseType: "blob",
headers: { Authorization: "Bearer " + graphToken }
});
if (response.status != 200 && response.status != 204) {
return "error";
}
var urlCreator = window.URL || window.webkitURL;
var imageUrl = urlCreator.createObjectURL(response.data);
return await imageUrl;
} catch (error) {
return "error";
}
};
You want to check that _isMounted is still true before you call setState, not before you start the request.
async componentDidMount() {
this._isMounted = true;
let logo = await getImage(
`https://graph.microsoft.com/v1.0/groups/${this.props.teamId}/photo/$value`
);
if (this._isMounted && logo !== "error") {
this.setState({ groupImage: logo });
}
}

how to iterate through json data from api in react.js

I just started learning React and am trying to loop through an array of JSON data. However, I am getting some syntax errors. I'm trying to use the array.map function, but it's not working properly, and I'm not exactly sure how to implement it to make it display each element in the JSON array instead of just one. Any help is greatly appreciated - thanks!
import React, { Component } from 'react';
import axios from "axios";
import './App.css';
import UserForm from "./components/UserForm.js";
class App extends Component {
state = {
name: "",
stars: "",
icon: "",
trails: [], isLoaded: false
}
getUser = (e) => {
e.preventDefault();
const address = e.target.elements.address.value;
if (address) {
axios.get(`https://www.hikingproject.com/data/get-trails?lat=40.0274&lon=-105.2519&maxDistance=10&key=200279581-dd891420fa2c470dbb683b34e017062a`)
.then((res) => {
console.log(res);
const trailList = res.data.trails.map((trail) => {
console.log(trail.name)
console.log(trail.stars)
return <div> <p>{trail.name}</p> </div>
})
this.setState({ trails: trailList, isLoaded: true });
const name = res.data.trails.name;
const stars = res.data.trails.stars;
const icon = res.data.trails.imgMedium;
this.setState({ name });
this.setState({ stars });
this.setState({ icon });
})
}
else return;
}
render() {
return (
<div>
<div className="App">
<header className="App-header">
<h1 className="App-title">HTTP Calls in React</h1>
</header>
<UserForm getUser={this.getUser} />
<div className="newmessage">
{this.state.trails.map((obj) => {
return(
<div>
<p>{obj.name}</p> >
<p> {obj.stars}</p>
</div>
);
}}
</div>
</div>
</div>
</div>
);
}
};
export default App;
A good start would be to fetch your data in the componentDidMount either with fetch or axios. Never used axios, so I am going to answer the question with fetch
Leave the constructor as it is. Then write a componentDidMount like so:
componentDidMount() {
fetch('https://www.hikingproject.com/data/get-trails?lat=40.0274&lon=-105.2519&maxDistance=10&key=200279581-dd891420fa2c470dbb683b34e017062a')
.then(res => res.json())
.then(data => this.setState({ trails: data.trails }))
.catch(e => console.log(e))
}
then in a sub-render method, such as renderData, write the following code:
renderData() {
if (!this.state.trails) {
return null;
}
return this.state.trails.map(trail => <p>{trail.name}</p>);
}
Then call {this.renderData()} in your render
render() {
return (
<div>{this.renderData()}</div>
)
}
This code has been tested on my local environment and it was working as it should.

Resources