I have an application which needs to fetch invoice data from Stripe API (payment processor). When the invoice data has been returned, I'm trying to update my state using this.setState({invoiceData: invoices}) where invoices is a string of HTML that I build out from the data returned from the Stripe API.
The issue is that the HTML isn't being rendered and is showing as plain text. I am pretty new to React and have only just got my head around rendering states, but now I'm pretty stuck on working this one out. What do I need to do to render the HTML? Please see my code below.
import React from 'react';
class BillingInvoices extends React.Component {
constructor(props) {
super(props);
this.state = {
invoiceData: false
}
}
// When the 'BillingInvoices' component is mounted:
componentDidMount() {
// Get invoice data from Stripe API.
fetch('/stripe-invoices', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
customerId: '128973982'
})
})
.then((response) => {
if (response.ok) {
return response.json();
} else {
console.log('Error with Stripe response');
}
})
.then((stripeData) => {
var invoiceCount = stripeData['result']['data'].length;
var i;
var invoices = '';
for (i = 0; i < invoiceCount; i++) {
invoices += '<div><a href="' + stripeData['result']['data'][i]['invoice_pdf'] + '" download>' + stripeData['result']['data'][i]['number'] + '</a></div>';
}
this.setState({
invoiceData: invoices
})
})
.catch((error) => {
console.log('Error: ', error);
});
}
render() {
return (
<div id="billing-invoices">
{this.state.invoiceData ? this.state.invoiceData : null}
</div>
);
}
}
export default BillingInvoices;
Thank you for any insight.
I've stripped out some of your code for my example to make it easier to read:
class BillingInvoices extends React.Component {
constructor(props) {
super(props);
this.state = { invoiceData: [] }
}
componentDidMount() {
fetch('/stripe-invoices')
.then((response) => response.ok && response.json())
// Here I'm assigning the nested array to `invoiceData` immediately
// so that you don't need to map over it later
.then((data) => this.setState({ invoiceData: data.result.data }));
}
render() {
// Here we can check if the data exists. If it doesn't
// show a loading icon (or something) until it is
if (!this.state.invoiceData) <Loader />
// ...otherwise show the data
return (
<div id="billing-invoices">
// we map over the invoice data and for each invoice
// return JSX (your div with an anchor populated with that invoice data)
{this.state.invoiceData.map((invoice) => {
return (
<div>
<a href={invoice.invoice_pdf} download>{invoice.number}</a>
</div>
)
})}
);
</div>
)
}
}
You can populate invoiceData with react components using JSX like so:
let invoices = (<div>{stripeData['result']['data'].map(data => (<div><a href={data['invoice_pdf']} download>{data['number']}</a></div>))}</div>);
this.setState({invoiceData: invoices});
You can replace the content of the second then clause with the above and leave the rest of the code unchanged.
Putting the resulted json in the component state is a good idea.
But then, you should deal with this json directly in your render method, using the power of JSX.
Check the official documentation about how to use JSX.
This is a dummy example of what your component could look like with the usage of JSX:
import React from "react";
class BillingInvoices extends React.Component {
constructor(props) {
super(props);
}
state = {
invoices: []
}
// When the 'BillingInvoices' component is mounted:
componentDidMount() {
// Get invoice data from Stripe API.
fetch("/stripe-invoices", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
customerId: "128973982"
})
})
.then(response => {
if (response.ok) {
this.setState(invoices: response.json());
} else {
console.log("Error with Stripe response");
}
})
.catch(error => {
console.log("Error: ", error);
});
}
render() {
return (
<div id="billing-invoices">
{this.state.invoices.map((invoice, index) => {
return (
<div key={index}>{invoice.name}</div>
)
})}
</div>
);
}
}
export default BillingInvoices;
Related
im having trouble figuring out how to fetch a url that contains an array in react
the parent component fetches data that gets sent to two components.
export default class ParentComponent extends Component<AuthProps, ChannelState> {
constructor(props: AuthProps) {
super(props)
this.state = {
...
}
}
getChannel = () => {
console.log("get channel called")
fetch(`${APIURL}/channel/mine`, {
method: "GET",
headers: new Headers({
"Content-Type": "application/json",
"Authorization": `${this.props.sessionToken}`
})
})
.then(response => response.json())
.then(data => {
console.log(data)
this.setState({
channel: data
})
console.log(this.state.channel, "channel called")
})
.catch(err => console.log(err))
}
the state gets sent to two child components. childcomponent1 is a route that uses channelId in the fetch method. childcomponent2 displays a dynamic link to component1 using channelId as a key
export default class ChildComponent1 extends Component<AuthProps, ChannelEntryState> {
constructor(props: AuthProps) {
super(props)
this.state = {
...
}
}
getChannelEntry = () => {
console.log("get channel entry called")
console.log(this.props.channel.length)
fetch(`${APIURL}/channel/${this.props.channel[1].channelId}/channelentry`, {
method: "GET",
headers: new Headers({
"Content-Type": "application/json",
"Authorization": `${this.props.sessionToken}`
})
})
.then(response => response.json())
.then(data => {
console.log(data)
this.setState({
channelEntry: data.messages
})
console.log(this.state.channelEntry, "channel entry called")
})
.catch(err => console.log(err))
}
const ChildComponent2 = (props: AuthProps) => {
return(
<Row>
{props.channel.map((cprops: ChannelType) => {
return(
<>
<Col>
<div>
<ul className="sidebar-list list-unstyled" key={cprops.channelId}>
<li><Link to={`/channelEntry/${cprops.channelId}`}><Button onClick={() => {console.log('button clicked')}}>{cprops.name}</Button></Link></li>
</ul>
</div>
</Col>
</>
)
})}
Ive looked into useParams but i believe its only possible in a functional component. I believe i shouldnt use functional components when states can change. How can i fetch the url in component1 dynamically.
Concerning params you can access them in a react class component using this.props.match.params.
And concerning the useParams, two things
yes, anything with a use in front of a name should be a hook and can only be used in functional components.
no, functional components, since React v17, can have their own states, using the useState hook.
just keep in mind that the you can have multiple states in functional components so you should use a state for each controlled part.
for people using react typescript class components look into this link
React-router-v6 access a url parameter
I'm completely new to JS and React and im trying to upload a file with my MS custom teams app.
I've found the information i need to make it work, i just dont understand how i can use it within my teams tab.
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 });
//var inputs {}
const queryParameters = new URLSearchParams({ function: "getDocuments", input: '"'+ context.userPrincipalName + '"',});
console.log(`userPrincipalName is '${context.Id}'`);
console.log(`teamName is '${context.teamName}'`);
console.log(`http://localhost/openims/json.php?${queryParameters}`);
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>
{/* Your Link */}
{name} {age} {city}
</li>
</ul>
);
}
}
}
export default Tab;
Currently im using a componentDidMount to fetch some info i need from a URL, but now i need to figure out how i add another componentDidMount(i think) to do a PUT and upload a file to my drive location. Preferably the drive location of my MS teams team onedrive.
So somewhere i have to put this:
PUT /me/drive/root:/FolderA/FileB.txt:/content
Content-Type: text/plain
The contents of the file goes here.
So i can actually upload a file. How do i go about this?
You can not add multiple componentDidMount() methods however in success callback you can call another API to upload the file.
Or you can call after promise in same componentDidMount() method.
Also you can write your code like below:
fetch('https://me/drive/root:/FolderA/FileB.txt:/', {
method: 'PUT',
body: fileContent
})
.then((response) => response.json())
.then((result) => {
console.log('Success:', result);
})
.catch((error) => {
console.error('Error:', error);
});
You can refer below documentation:
https://learn.microsoft.com/en-us/graph/api/driveitem-put-content?view=graph-rest-1.0&tabs=http#example-upload-a-new-file
Similar issue reference URL:
How do I upload a file with the JS fetch API?
I have a fairly simple ASP.NET site with a react front-end. It has a component MetaWeatherForecast that fetches some data from an API endpoint and displays it in a table. That works fine.
After pulling in react-pull-to-refresh into the project and attaching it to the component, the table initially loads and fetches the data on the first load, but then fails as soon as I pull the table to refresh.
Here's a trimmed version of the component in its current form:
MetaWeatherForecast.js
import React, { Component } from 'react';
import authService from './api-authorization/AuthorizeService'
import Moment from 'moment';
import ReactPullToRefresh from 'react-pull-to-refresh'
export class MetaWeatherForecast extends Component {
static displayName = MetaWeatherForecast.name;
constructor(props) {
super(props);
this.state = {
locationForecast: {}, loading: true, success: true, errorMessage: null };
}
componentDidMount() {
this.populateWeatherData();
}
static renderForecastsTable(locationForecast) {
// html markup for the table
}
static renderError(errorMessage) {
// error markup
}
handleRefresh(resolve, reject) {
let success = this.populateWeatherData();
if (success)
resolve();
else
reject();
}
async populateWeatherData() {
this.setState({ locationForecast: {}, loading: true, success: true, errorMessage: null});
const token = await authService.getAccessToken();
const response = await fetch('api/metaweatherforecast/GetFiveDayForecast/44544', {
headers: !token ? {} : { 'Authorization': `Bearer ${token}` }
});
const baseResponse = await response.json();
console.log(baseResponse);
this.setState({ locationForecast: baseResponse.data, loading: false, success: baseResponse.success, errorMessage: baseResponse.errorMessage });
return baseResponse.success;
}
getContent() {
let contents;
if (this.state.loading) {
contents = <p><em>Fetching forecast...</em></p>
} else {
contents = this.state.success
? MetaWeatherForecast.renderForecastsTable(this.state.locationForecast)
: MetaWeatherForecast.renderError(this.state.errorMessage);
}
return contents;
}
render() {
return (
<ReactPullToRefresh
onRefresh={this.handleRefresh}
style={{
textAlign: 'center'
}}>
<div>
<p><em>Pull down to refresh</em></p>
<h1 id="tabelLabel" >Meta Weather forecast</h1>
{this.getContent()}
</div>
</ReactPullToRefresh>
);
}
};
The error being thrown after pulling the table is as follows and is thrown inside the handleRefresh() method:
Uncaught (in promise) TypeError: this.populateWeatherData is not a function
Any ideas or suggestions would be most welcome
In react classes, you have to bind this in the constructor
constructor(props) {
...
this.<method> = this.<method>.bind(this);
}
I like using this library.
I understand how to pass data from component to component using this.props.
However, I can't seem to figure out a way to pass values or gather values asynchronously from say a database to a variable that will be used throughout the component,
I can't change this variable after load it has to be loaded before the render method since it is not allowed to be changed once loaded.
What I mean is this attempt 1
import React from "react";
import ReactDOM from "react-dom";
let my_variable = "";
fetch("url", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({})
})
.then(data => data.json())
.then(result => {
return (my_variable = result);
});
--------------------new way-------------------------
const my_variable = fetch("url", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({})
})
.then(data => data.json())
.then(result => {
return (result);
});
-------------------------------------------------
class MyClass extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {};
}
render() {
return (
<>
<NewElement variableNeeded={my_variable} />
</>
);
}
}
export default MyClass;
It, of course, does not work since the empty variable hits the component and the cannot be updated.
Option B:
import React from "react";
import ReactDOM from "react-dom";
class MyClass extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {};
}
render() {
return (
<>
<NewElement variableNeeded={this.props.my_variable} />
</>
);
}
}
export default MyClass;
Again does not work, this class is a child of another class and the variable value was gathered inside the onComponentDidMount() the render method was hit first so I don't know how to gather the data then pass it.
If anyone could help me with this I would greatly appreciate it
To fetch the data:
componentDidMount() {
fetch('url', /* your config here */).then(data => {
return data.json();
}).then(json => {
this.setState({ variable: json });
});
}
... Then in the render method ...
render() {
const { variable } = this.state;
return (
<>
{ variable && <NewElement variableNeeded={variable} /> }
</>
);
}
for handling gathering asynchronously data from somewhere you need to use React Life Cycles like so :
class MyClass extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
variable: ""
};
}
componentDidMount() {
fetch("url", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({})
})
.then(data => data.json())
.then(result => {
return this.setState({ variable: result });
});
}
render() {
return <>{variable && <NewElement variableNeeded={variable} />}</>;
}
}
export default MyClass;
I'm not sure if this is ok also but now I don't get the message from the component that it is being re-rendered, using method A and change the top part to the following
--------------------new way-------------------------
const my_variable = fetch("url", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({})
})
.then(data => data.json())
.then(result => {
return (result);
});
-------------------------------------------------
I wonder if anyone knows how to fix this. I have 2 functions in my javascript and I make sure to bind both. Would appreciate it if anyone knows what unmounted component means and what possibly caused this. Is there anything wrong with how I structure my promises? It looks fine when I rendered my site locally.
Thank you!
E AssertionError: Error in browser console:
E webpack:///./node_modules/react-dom/cjs/react-dom.development.js? 178:25 "Warning: Can't
perform a React state update on an unmounted component. This is a no-op, but it indicates a
memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in
%s.%s" "the componentWillUnmount method" "\n in Likes (created by Post)"
my code:
import React from 'react';
import PropTypes from 'prop-types';
class Likes extends React.Component {
/* Display number of likes a like/unlike button for one post
* Reference on forms https://facebook.github.io/react/docs/forms.html
*/
constructor(props) {
super(props);
this.state = { num_likes: 0, logname_likes_this: false };
this.like = this.like.bind(this);
this.unlike = this.unlike.bind(this);
}
componentDidMount() {
fetch(this.props.url, { credentials: 'same-origin' })
.then((response) => {
if (!response.ok) throw Error(response.statusText);
return response.json();
})
.then((data) => {
console.log(data)
this.setState({
num_likes: data.likes_count,
logname_likes_this: data.logname_likes_this,
});
})
.catch(error => console.log(error));
}
like(event) {
event.preventDefault();
fetch(this.props.url, {
method: "POST",
header: {
'Content-Type': 'application/json'
},
})
.then(()=>{
this.setState({
num_likes: this.state.num_likes + 1,
logname_likes_this: 1,
})
})
.catch(error => console.log(error));
}
unlike(event) {
event.preventDefault();
fetch(this.props.url, {
method: "DELETE",
header: {
'Content-Type': 'application/json'
},
})
.then(()=>{
this.setState({
num_likes: this.state.num_likes - 1,
logname_likes_this: 0,
})
})
.catch(error => console.log(error));
}
render() {
let button;
if (this.state.logname_likes_this) {
button = <button onClick={this.unlike} className="like-unlike-button">unlike</button>
} else {
button = <button onClick={this.like} className="like-unlike-button">like</button>
}
return (
<div className="likes">
<p>{this.state.num_likes} like{this.state.num_likes !== 1 ? 's' : ''}</p>
{button}
</div>
);
}
}
Likes.propTypes = {
url: PropTypes.string.isRequired,
};
export default Likes;