React.js - Loading single post data from API correctly - javascript

I am fairly new to React, and trying to work my way through how I should properly be loading data from my API for a single post.
I have read that I should be using "componentDidMount" to make my GET request to the API, but the request is not finished by the time the component renders. So my code below does not work, as I am recieving the error: "Cannot read property setState of undefined".
What I am doing wrong here? Should I be calling setState from somewhere else? My simple component is below - thanks.
import React from 'react';
import Header from './Header';
import axios from 'axios';
class SingleListing extends React.Component {
constructor(props) {
super(props);
this.state = {
listingData: {}
}
}
componentDidMount() {
// Get ID from URL
var URLsegments = this.props.location.pathname.slice(1).split('/');
// Load the listing data
axios.get('/api/listing/' + URLsegments[1])
.then(function(res){
let listingDataObject = res.data;
console.log(listingDataObject);
this.setState({
listingData: listingDataObject
});
})
.catch(function(err){
console.log(err);
});
}
render() {
console.log('helsdfdsfsdflssosso');
console.log(this.state.listingData);
return (
<div className="SingleListing">
<Header />
<div className="container">
<div>Property Address: {this.state.listingData.propertyAddress}</div>
This is a single listing
</div>
</div>
)
}
}
export default SingleListing;

You just need to change what you render depending on whether the data is loaded or not yet.
Also, you should use arrow functions when handling the axios response, otherwise this is not set correctly.
class SingleListing extends React.Component {
constructor(props) {
super(props);
this.state = {
listingData: null,
};
}
componentDidMount() {
// Get ID from URL
const URLsegments = this.props.location.pathname.slice(1).split('/');
// Load the listing data
axios
.get(`/api/listing/${URLsegments[1]}`)
.then(res => {
const listingDataObject = res.data;
console.log(listingDataObject);
this.setState({
listingData: listingDataObject,
});
})
.catch(err => {
console.log(err);
});
}
render() {
const isDataLoaded = this.state.listingData;
if (!isDataLoaded) {
return <div>Loading...</div>;
}
return (
<div className="SingleListing">
<Header />
<div className="container">
<div>Property Address: {this.state.listingData.propertyAddress}</div>
This is a single listing
</div>
</div>
);
}
}
export default SingleListing;

this is out of scope you need to include it. here is a solution using es2015 arrow functions =>
axios.get('/api/listing/' + URLsegments[1])
.then((res) => {
let listingDataObject = res.data;
console.log(listingDataObject);
this.setState({
listingData: listingDataObject
});
})
.catch((err) => {
console.log(err);
});

Related

Getting my userName from MS teams with javascript/reactjs

Im trying to get my Teams userPrincipalname out of the context and using it in a fetch URL. Unfortunately its not actually saving my userPrincipalName within {userPrincipalName} but instead it contains: [object Object]
As i can see in the URL its trying to fetch: http://localhost/openims/json.php?function=getDocuments&input=%22[object%20Object]%22
The URL returns the following: {"name":"[object Object]","age":26,"city":"London"}
What am i doing wrong here?
The code:
import React from 'react';
import './App.css';
import * as microsoftTeams from "#microsoft/teams-js";
class Tab extends React.Component {
constructor(props){
super(props)
this.state = {
context: {}
}
}
componentDidMount(){
microsoftTeams.getContext((context, error) => {
this.setState({
context: context
});
});
}
componentDidMount() {
const { userPrincipalName } = this.state.context;
fetch('http://localhost/openims/json.php?function=getDocuments&input='+'"'+ {userPrincipalName} +'"')
.then(res => res.json())
.then((result) => {
this.setState({ ...result });
})
.catch((error) => {
this.setState({ error });
})
.finally(() => {
this.setState({ isLoaded: true })
});
}
render() {
const { error, isLoaded, name, age, city } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
<li>
{name} {age} {city}
</li>
</ul>
);
}
}
}
export default Tab;
The problems I can see are that you need to ensure:
microsoftTeams.getContext takes a callback, so promisify it and then you use then on it (like any promise)
once you've got the context, create a URL dynamically using the value of context.userPrincipalName
the final fetch request (to /openims/json.php endpoint) only happens once all of the above has happened
That should be something like the following (although re-writing your component as functional component would allow you to use React hooks and better handle any cleanup required).
import React from "react";
import "./App.css";
import * as microsoftTeams from "#microsoft/teams-js";
class Tab extends React.Component {
constructor(props) {
super(props);
this.state = { context: {} };
}
componentDidMount() {
new Promise((resolve) => {
microsoftTeams.getContext(resolve);
})
.then((context) => {
this.setState({ context });
const queryParameters = new URLSearchParams({
function: "getDocuments",
input: `"${context.userPrincipalName}"`,
});
console.log(`userPrincipalName is '${context.userPrincipalName}'`);
return fetch(`http://localhost/openims/json.php?${queryParameters}`);
})
.then((res) => res.json())
.then((result) => this.setState({ ...result }))
.catch((error) => this.setState({ error }))
.finally(() => this.setState({ isLoaded: true }));
}
render() {
const { error, isLoaded, name, age, city } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
<li>
{name} {age} {city}
</li>
</ul>
);
}
}
}
export default Tab;
Could you please try with below working code.
import React from 'react';
import * as microsoftTeams from "#microsoft/teams-js";
class Tab extends React.Component {
constructor(props){
super(props)
this.state = {
context: {}
}
}
//React lifecycle method that gets called once a component has finished mounting
//Learn more: https://reactjs.org/docs/react-component.html#componentdidmount
componentDidMount(){
// Get the user context from Teams and set it in the state
microsoftTeams.getContext((context, error) => {
this.setState({
context: context
});
});
// Next steps: Error handling using the error object
}
render() {
let userName = Object.keys(this.state.context).length > 0 ? this.state.context['upn'] : "";
return (
<div>
<h3>Hello World!</h3>
<h1>Congratulations {userName}!</h1> <h3>This is the tab you made :-)</h3>
</div>
);
}
}
export default Tab;

Accessing a variable from outside a function - Javascript, API, XMLHttpRequest

Just wanting to access the 'data' variable from this XMLHttpRequest function. Need to use the data in my 'App' react component which is below.
I have tried making the 'let data' global?
But just need to be able to access it outside of the function where the 'outside' console.log is. Currently, it shows undefined.
So asking how to make my data be accessed by my react component 'App'.
Sorry, I am new to using this function to get API data.
Thank you for the help, and if you need any more information please ask!
const request = new XMLHttpRequest()
request.open('GET', 'https://ghibliapi.herokuapp.com/films', true)
let data
request.onload = function () {
let data = JSON.parse(this.response)
if (request.status >= 200 && request.status < 400) {
data.forEach((movie) => {
console.log(movie.title)
})
} else {
console.log('error')
}
}
console.log(data,"outside")
request.send()
class App extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<>
<DateRangePickerWrapper value={this.state.child1}/>
</>
)
}
}
You're using class programming on your react aplication, so you must do somenthing
like this
import axios from 'axios'
class App extends Component {
constructor(props){
super(props);
this.state = {data: []}
}
componentDidMount(){
axios.get("https://ghibliapi.herokuapp.com/films")
.then(response =>{
this.setState({data: response.data});
})
.catch(function(error){
console.log(error)
})
}
componentDidUpdate(){
axios.get("https://ghibliapi.herokuapp.com/films")
.then(response =>{
this.setState({data: response.data});
})
.catch(function(error){
console.log(error)
})
}
render() {
return (
<div>
{data.map(data=> (
//logic to render the api's data
))}
</div>
)
}
}
This is how to do it
import React, { useEffect, useState } from "react";
export default function App() {
const [readData, writeData] = useState();
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => response.json())
.then((json) => writeData(json));
}, []);
return (
<>
<div>title: {readData.title}</div>
<div>completed: {String(readData.completed)}</div>
</>
);
}
Sandbox
I struggled with this exact question when I was first dealing with async code.
If you have async code in React the formula is simple. Have some state, when the async code runs, update the state and the changes should be reflected in your jsx.

How to call a function in a function in React (auth, routing)

I am trying to create a component that executes straight when DOM is loaded, onInit();
This function posts a token to an endpoint, then if successful, I am trying to run a function called 'valid()'
The problem I keep getting is, when I try to call the 'valid' function in response, it says cannot history of undefined.
I think I am not passing props in the right way.
Also if unsuccessful, an Error page should be returned.
Thanks for any help on this
export class LandingPage extends Component {
constructor(props) {
super(props);
this.state = {};
this.valid = this.valid.bind(this);
}
valid = () => {
auth.login(() => {
this.props.history.push("/app");
});
};
componentDidMount() {
onInit();
function onInit(props) {
const apiUrl = "www.somefakedomain.com/endpoint"
axios
.post(apiUrl, {
token: 'somevalue123'
})
.then(function(response) {
console.log(response);
//CALL VALID FUNCTION HERE
this.valid; //throws error, how to run function here
})
.catch(function(error) {
console.log(error);
//Show Error Page
});
}
}
render() {
return (
<div>
<Spinner />
</div>
);
}
}
You are not passing anything to your onInIt function.
Are you perhaps trying to do something like this? -
export class LandingPage extends Component {
constructor(props) {
super(props);
this.state = {};
this.valid = this.valid.bind(this);
}
valid = () => {
auth.login(() => {
this.props.history.push("/app");
});
};
componentDidMount() {
function onInit(props) {
const apiUrl = "www.somefakedomain.com/endpoint"
axios
.post(apiUrl, {
token: 'somevalue123'
})
.then(function(response) {
console.log(response);
//CALL VALID FUNCTION HERE
this.valid(); //need to call function not reference it//throws error, how to run function here
})
.catch(function(error) {
console.log(error);
//Show Error Page
});
}
onInIt(this.props);
}
render() {
return (
<div>
<Spinner />
</div>
);
}
}
javascript reactjs function authent

API taking too long, map function firing before data loads

import React, { Component } from 'react';
import {withProvider} from './TProvider'
import ThreeCardMap from './ThreeCardMap';
class Threecard extends Component {
constructor() {
super();
this.state = {
newlist: []
}
}
componentDidMount(){
this.props.getList()
this.setState({newlist: [this.props.list]})
}
// componentDidUpdate() {
// console.log(this.state.newlist);
// }
render() {
const MappedTarot = (this.state.newlist.map((list, i) => <ThreeCardMap key={i} name={list.name} meaningup={list.meaning_up} meaningdown={list.meaning_rev}/>);
return (
<div>
<h1>Three Card Reading</h1>
<div>{ MappedTarot }</div>
</div>
)
}
}
export default withProvider(Threecard);
Hi, I'm trying to create a page that takes data from a tarot card API (https://rws-cards-api.herokuapp.com/api/v1/cards/search?type=major). Unfortunately by the time the data comes in, my map function has already fired. I'm asking to see if there is a way to have the map function wait until the data hits before it fires. Thanks!
Edit: getList function in the Context:
getList = () => {
console.log('fired')
axios.get('https://vschool-cors.herokuapp.com?url=https://rws-cards-api.herokuapp.com/api/v1/cards/search?type=major').then(response =>{
this.setState({
list: response.data
})
}).catch(error => {
console.log(error);
})
}
this.props.getList() is an async function. You are setting the list right after that call which is not correct.
You need to set it in the getList promise then() block.
getList() is an async function and update data for the parent component. So, my solution is just watching the list from the parent component if they updated or not, through getDerivedStateFromProps
class Threecard extends Component {
constructor() {
super();
this.state = {
newlist: []
}
}
// Set props.list to this.state.newList and watch the change to update
static getDerivedStateFromProps(nextProps, prevState) {
return {
newlist: nextProps.list
}
}
componentDidMount(){
this.props.getList()
// Removed this.setState() from here.
}
render() {
const MappedTarot = (this.state.newlist.map((list, i) => <ThreeCardMap key={i} name={list.name} meaningup={list.meaning_up} meaningdown={list.meaning_rev}/>);
return (
<div>
<h1>Three Card Reading</h1>
<div>{ MappedTarot }</div>
</div>
)
}
}
export default withProvider(Threecard);

How do I manipulate data that is located in state and display to page?

I'm making three separate axios calls that each set the state with some data. Where do I do my data manipulation with the state data not to change the state but to display something else where?
For example out of the transactionItems state, I want to get all transactions for the current date. All transaction items have the date set automatically when its added to the database.
I'm having issues parsing the data because my setstate seems to update 3 times with all the axios calls.
There are other data manipulations I would like to be able to do as well but I feel like I'll hit another roadblock.
import React, { Component } from "react";
import axios from "axios";
import moment from "moment";
import TransactionSummary from "./TransactionSummary";
import BudgetSummary from "./BudgetSummary";
import DebtSummary from "./DebtSummary";
class DashboardTable extends Component {
constructor(props) {
super(props);
this.state = {
transactionItems: [],
budgetItems: [],
debtItems: [],
spentToday: ""
};
}
componentDidMount() {
this.getTransactionData();
this.getBudgetData();
this.getDebtData();
}
getTransactionData = () => {
axios.defaults.headers.common["Authorization"] = localStorage.getItem(
"jwtToken"
);
axios
.get("/api/transactions")
.then(res =>
this.setState({
transactionItems: res.data
})
)
.catch(err => console.log(err));
};
getBudgetData = () => {
axios.defaults.headers.common["Authorization"] = localStorage.getItem(
"jwtToken"
);
axios
.get("/api/budgets")
.then(res =>
this.setState({
budgetItems: res.data
})
)
.catch(err => console.log(err));
};
getDebtData = () => {
axios.defaults.headers.common["Authorization"] = localStorage.getItem(
"jwtToken"
);
axios
.get("/api/debts")
.then(res =>
this.setState({
debtItems: res.data
})
)
.catch(err => console.log(err));
};
render() {
return (
<div>
<div className="content">
<TransactionSummary transactionItems={this.state.transactionItems} />
<BudgetSummary budgetItems={this.state.budgetItems} />
<DebtSummary debtItems={this.state.debtItems} />
</div>
</div>
);
}
}
export default DashboardTable;
Here's DebtSummary component
import React from "react";
const DebtSummary = props => {
let sumOfDebtItems = props.debtItems.reduce((a, c) => {
return a + c["balance"];
}, 0);
return (
<div>
<p>Debt Summary</p>
{sumOfDebtItems}
</div>
);
};
export default DebtSummary;
Like Hemadri said, the easiest way to do this is to move the 3 axios calls into their respective component
You can also move the data manipulation into a separate method and call it in the render method. You can write as many of these as you need, they can all read from the same state variable
DebtSummary example:
import React from "react";
class DebtSummary extends React.Component {
constructor(props) {
super(props);
this.state = {
debtItems: []
}
}
getDebtData = () => {
axios.defaults.headers.common["Authorization"] = localStorage.getItem(
"jwtToken"
);
axios
.get("/api/debts")
.then(res =>
this.setState({
debtItems: res.data
})
)
.catch(err => console.log(err));
};
// Do some data manipulation, in the case computing the debt sum
sumOfDebtItems = () => {
return this.state.debtItems.reduce((a, c) => {
return a + c["balance"];
}, 0);
}
// Load the debt data once the component has mounted
componentDidMount() {
this.getDebtData()
}
render() {
return (
<div>
<p>Debt Summary</p>
{this.sumOfDebtItems()}
</div>
);
}
};
export default DebtSummary;

Categories

Resources