Display properties from state using React - javascript

I have a simple React component which pulls in some data via the Monzo API and then I simply want to print it out on the screen. I can see I'm getting back the correct data via the React dev tools and it's setting the state however nothing gets printed in my HTML.
Here is my component:
import React, {Component} from "react";
import "./App.css";
import axios from "axios";
class App extends Component {
constructor(props) {
super(props);
this.state = {
account: [{
id: '',
description: '',
created: '',
type: ''
}]
}
}
componentDidMount() {
let config = {
headers: {
'Authorization': 'Bearer sampleToken'
}
};
axios.get('https://api.monzo.com/accounts', config).then((response) => {
this.setState({'account': response.data.accounts});
});
}
render() {
return (
<div className="App">
<div className="App-header">
<h2>Hello {this.state.account.map(account =>
console.log(account),
<p>account.description</p>
)}</h2>
</div>
<p className="App-intro">
Monzo API app
{this.state.account.id}
</p>
</div>
);
}
}
export default App;
My console log within my map function first displays an empty account object and then a filled correct account object, could this be the reason why?

<p>{account.description}</p>
instead of
<p>account.description</p>

Related

Reactjs not re-rendering after this.setState()

I am using spotify's api and retrieving an array of strings to set into the state in order to be mapped into the HTML via JSX.
Logging it to the console shows that I do get the correct array stored into the state, but React never re-renders to display them. If I would setstate again after with my own values, the page works just fine. Maybe it is a problem with async?
import React from 'react';
import Button from 'react-bootstrap/esm/Button';
import Spotify from 'spotify-web-api-js';
import Track from './Track';
import Api from '../Api.js'
export default class OtherPage extends React.Component{
constructor(props){
super(props);
this.state = {
artists:['artists']
};
this.getArtists = this.getArtists.bind(this);
}
async getArtists(){
let api = new Api();
let arr = await api.getTopArtists();
console.log('arr', arr);
this.setState({artists: arr});
console.log('new-arr', this.state.artists);
// this.setState({artists: ['noe', 'tow', 'tre']})
// console.log('new-new-arr', this.state.artists)
}
render(){
return(
<div>
<h1>My Spotify React App!</h1>
<div className="tracks-container" style={{maxHeight: 500, overflow: 'scroll', margin:50, marginTop:25}}>
{this.state.artists.map((artist) =>
<p>Artist: {artist}</p>
)}
<button onClick={() => this.getArtists()}>Get Top Artists</button>
</div>
</div>
);
};
}
here is the code for getTopArtists()
import React from 'react';
import { Component } from 'react';
import Button from 'react-bootstrap/Button';
import 'bootstrap/dist/css/bootstrap.min.css';
import { Redirect, BrowserRouter as Router, Route, Switch, useHistory } from 'react-router-dom';
class Api extends React.Component{
async getTopArtists(){
let arr = [];
let artists = await fetch("https://api.spotify.com/v1/me/top/artists", {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer '+ localStorage.getItem('access_token')
}
}
).then((response) => {
console.log(response.json().then(
(data) => data.items.forEach(item => arr.push(item.name))
)
)});
console.log(arr);
return arr;
}
}
export default Api;
It's hard to say for sure, but two changes I would try:
First,
this.setState({artists: [...arr]});
this forces a new array to be created, just in case the api.getTopArtists(); is somehow reusing the same array for it's results, which could cause React to not detect the change.
second,
{this.state.artists.map((artist) =>
<p key={artist}>Artist: {artist}</p>
)}
Since without a key on a list, it's harder for react to know what changed in the list when the backing array changes. Probably not the issue, but could be.
React has a special method named 'componentDidMount()' for the purpose of making calls to external APIs.
Calling the external API and setState subsequently from the componentDidMount() method will help achieve the desired result.
Working example with componentDidMount() :
export default class OtherPage extends React.Component{
constructor(props){
super(props)
this.state = {
artists:['artists']
}
}
componentDidMount() {
let api = new Api()
api.getTopArtists()
.then((arr) => {
this.setState({artists: arr})
})
}
render() {
return(
<div>
{this.state.artists.map((artist) =>
<p>Artist: {artist}</p>
)}
</div>
)
}
}
More information:
https://reactjs.org/docs/faq-ajax.html

Update React Component When Using react-responsive-tabs

In my react app when I make a serverside update I return a response which I use to update the state of the parent component. But for my components where I use react-responsive-tabs they don't get updated.
Here's my react code:
import React, {Component, Fragment} from 'react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import PageTitle from '../../../Layout/AppMain/PageTitle';
import {
faAngleUp,
faAngleDown,
faCommentDots,
faBullhorn,
faBusinessTime,
faCog
} from '#fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '#fortawesome/react-fontawesome';
import Tabs from 'react-responsive-tabs';
import Roles from './Roles';
import Priviledges from './Priviledges';
export default class Apage extends Component {
constructor(props) {
super(props);
this.state = {
api: this.props.api,
session: this.props.session
}
}
componentWillMount() {
this.tabsContent = [
{
title: 'Roles',
content: <Roles api={this.state.api} session={this.state.session} />
},
{
title: 'Priviledges',
content: <Priviledges api={this.state.api} session={this.state.session} />
}
];
}
getTabs() {
return this.tabsContent.map((tab, index) => ({
title: tab.title,
getContent: () => tab.content,
key: index,
}));
}
onTabChange = selectedTabKey => {
this.setState({ selectedTabKey });
};
render() {
return (
<Fragment>
<PageTitle
heading="Roles & Priviledges"
subheading=""
icon="lnr-apartment icon-gradient bg-mean-fruit"
/>
<Tabs selectedTabKey={this.state.selectedTabKey} onChange={this.onTabChange} tabsWrapperClass="body-tabs body-tabs-layout" transform={false} showInkBar={true} items={this.getTabs()}/>
</Fragment>
)
}
}
I have tried using this within my <Roles /> tag:
shouldComponentUpdate(nextProps, nextState) {
return nextProps.session!= this.props.session;
}
but I couldn't get it to work for me. Any clue?
I'm running my React JS within laravel using laravel-mix. I actually intend to update a dropdown whenever I submit a form using setState. I've done this many other times when I use React JSas a REST API.
I ended up using socket IO to trigger a setSate within my component after a response comes from the server. Although i'd prefer something neater.
You need to onChange like this - onChange={() => this.onTabChange()}
see below-
<Tabs onChange={() => this.onTabChange()} selectedTabKey={this.state.selectedTabKey} tabsWrapperClass="body-tabs body-tabs-layout" transform={false} showInkBar={true} items={this.getTabs()}/>

React Firebase Timestamp Usage

So I am trying to get a timestamp on the time a post is made, but fire.database.ServerValue.TIMESTAMP doesn't seem to be working in the addTicket function. When I post the ticket, it doesn't load to the Pending page, and just has variables ticketTitle & ticketBody in the Ask URL. I think I am just confused on how the timestamp works in firebase. How do I properly add the timestamp of the post to the database tuple?
Ask.js:
import React, { Component } from 'react';
import AskForm from '../../components/AskForm.js';
import fire from '../../config/Fire.js';
import { Link, withRouter } from 'react-router-dom'
class Ask extends Component {
constructor(props){
super(props);
this.addTicket = this.addTicket.bind(this);
this.database = fire.database().ref().child('tickets');
this.state = {
tickets: [],
userId: this.props.user.uid
}
}
componentDidMount(){
fire.database().ref('/users/' + this.props.user.uid).once('value').then(function(snapshot) {
var FirstName = (snapshot.val() && snapshot.val().userFirstName);
// ...
console.log(FirstName);
});
}
addTicket(title, body){
this.database.push().set({ ticketUserId: this.props.user.uid, ticketTitle: title, ticketBody: body, ticketStatus: 'pending', ticketTime: fire.database.ServerValue.TIMESTAMP});
alert("Your question has been submitted.")
this.props.history.push('/pending')
}
render() {
return (
<div>
<div className="m-container">
</div>
<div>
<AskForm addTicket={this.addTicket} />
</div>
</div>
);
}
}
export default withRouter(Ask);
AskForm.js
import React, { Component } from 'react';
class AskForm extends Component{
constructor(props){
super(props);
this.state = {
ticketBody: '',
ticketTitle: ''
};
this.handleChange = this.handleChange.bind(this);
this.writeTicket = this.writeTicket.bind(this);
}
// When the user input changes, set the ticketTitle or ticketBody
// to the value of what's in the input box.
handleChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
writeTicket(){
if(this.state.ticketTitle === '' || this.state.ticketBody === ''){
alert('Please complete all fields.')
} else {
// Call a method that sets the ticketTitle and ticketBody for a ticket to
// the value of the input
this.props.addTicket(this.state.ticketTitle, this.state.ticketBody);
// Set inputs back to an empty string
this.setState({
ticketBody: '',
ticketTitle: ''
})
}
}
render(){
return(
<div class="s-container">
<form>
<label for="ticketTitle">Title: </label>
<input
id="ticketTitle"
name="ticketTitle"
type="text"
placeholder="A short sentence to identify your issue"
value={this.state.ticketTitle}
onChange={this.handleChange}
/>
<br/>
<br/>
<label for="ticketBody">Description: </label>
<textarea
id="ticketBody"
name="ticketBody"
placeholder="Placeholder"
value={this.state.ticketBody}
onChange={this.handleChange}
/>
<button
className="m-btn"
onClick={this.writeTicket}>
Submit
</button>
</form>
</div>
)
}
}
export default AskForm;
Revisited my question:
I need to import firebase directly using import * as firebase from 'firebase'; instead of from my config file. Then just pushed the time value to the database with my other values. See below for example.
Code:
import * as firebase from 'firebase';
addMessage(body){
this.questionDatabase.child(this.state.questionId).child('messages').push().set({
messageUserId: fire.auth().currentUser.uid,
messageBody: body,
time: firebase.database.ServerValue.TIMESTAMP
});
}
This works to create a timestamp client side is using firestore: (In this case I export it from my main firebase.js file)
import firebase from "firebase/compat/app";
import "firebase/compat/firestore";
export const serverStamp = firebase.firestore.Timestamp
To use it after importing serverStamp:
var stampNow = serverStamp.now()

Passing state into child component as props not working

I'm trying to build an example CRUD app with React and React Router, and I can't figure out why state isn't passing into a child component the way I'm expecting it to. When I hit the edit route, it renders the Edit component, which grabs the kitten I want from the database and sends it's info to a Form component which is used both for editing an existing kitten or adding a new one.
Here's the Edit component:
import React, { Component } from 'react';
import axios from 'axios';
import { match } from 'react-router-dom';
import Form from './Form';
export default class Edit extends Component {
constructor(props) {
super(props)
this.state = {}
}
componentDidMount() {
axios.get(`/updateKitten/${this.props.match.params.id}`)
.then(res => {
const kitten = res.data
this.setState({ kitten })
console.log(this.state.kitten.name) //Sammy, or something
})
.catch(err => console.log(err))
}
render() {
return (
<Form
name={this.state.kitten.name} // throws error or undefined
description={this.state.kitten.description} //throws error or undefined
route={this.props.match.params.id}
/>
)
}
}
The Edit component passes name, description, and route to this Form component:
import React, { Component } from 'react';
import axios from 'axios';
export default class Add extends Component {
constructor(props) {
super(props)
this.state = { name: this.props.name, description: this.props.description}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
const name = e.target.name;
const value = e.target.value;
this.setState({
[name]: value
});
}
handleSubmit(e) {
axios.post(`/addKitten/${this.props.route}`, this.state)
.then(this.setState({ name: '', description: '' }))
e.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>Name</label>
<input type='text' name="name" value={this.state.name}
onChange={this.handleChange}/>
<label>Description</label>
<input type='text' name="description" value={this.state.description}
onChange={this.handleChange}/>
<input type='submit' value='Submit' />
</form>
)
}
}
And I get the following error:
bundle.js:28950 Uncaught TypeError: Cannot read property 'name' of undefined
from trying to send that info as props to the Form component.
What am I doing wrong?
Two things,
First: In your edit component, you have not initialised kitten state and you are setting it based on the API result. However, componentDidMount is called after the component has been called and hence the DOM has been rendered once and the first time it did not find any value as this.state.kitten.name or this.state.kitten.description .
Its only after the API is a success that you set the kitten state. Hence just make a check while rendering.
Second: You have console.log(this.state.kitten.name) after the setState function. However setState is asynchronous. See this question:
Change state on click react js
and hence you need to specify console.log in setState callback
You code will look like
export default class Edit extends Component {
constructor(props) {
super(props)
this.state = {}
}
componentDidMount() {
axios.get(`/updateKitten/${this.props.match.params.id}`)
.then(res => {
const kitten = res.data
this.setState({ kitten }. function() {
console.log(this.state.kitten.name) //Sammy, or something
})
})
.catch(err => console.log(err))
}
render() {
return (
<Form
name={this.state.kitten.name || '' }
description={this.state.kitten.description || ''}
route={this.props.match.params.id}
/>
)
}
}
When your Edit component loads for the first time, it doesn't have a kitten. Hence the error.
What you need to do is the create an empty kitten. In your Edit component...
this.state = { kitten: {name:''} }
This will set the kitten's name to an empty string on the very first mount/render of your component.

Inter Components Communication With React

I am trying to get a very simple react app up-and-running.
The use case is straightforwards:
An auto-complete component that gets an array of account names, and upon value changed (user has selected the value) - fire event that will display the account.
Here is a code snippet, which I am trying to get work in a way that showAccount method will have access to App's state.
How can I access App's state from showAccount() ?
import React, { Component } from 'react';
import injectTapEventPlugin from 'react-tap-event-plugin';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import AutoComplete from 'material-ui/AutoComplete';
// Needed for onTouchTap
// http://stackoverflow.com/a/34015469/988941
injectTapEventPlugin();
import './App.css';
class App extends Component {
constructor () {
super();
this.state = {accounts: []}
}
componentDidMount() {
this.setState({ accounts: [
{account_name: "foo", account_id: 1},
{account_name: "bar", account_id: 2}
]})
}
showAccount (value) {
// HERE IS THE PROBLEM!
// `this` points to AutoComplete rather than to App
console.log(this.state.accounts)
}
render() {
return (
<MuiThemeProvider>
<div className="App">
<center>
<AutoComplete
floatingLabelText="account name"
filter={AutoComplete.caseInsensitiveFilter}
dataSource={this.state.accounts.map((account) => account.account_name)}
onUpdateInput={this.showAccount}
/></center>
</div>
</MuiThemeProvider>
);
}
}
export default App;
Don't you miss binding the showAccount method?
Check this code, there's an example of how to bind it, you need to do the same with your showAccount method.
class InputExample extends React.Component {
constructor(props) {
super(props);
this.state = { text: '' };
this.change = this.change.bind(this);
}
change(ev) {
this.setState({ text: ev.target.value });
}
render() {
let { text } = this.state;
return (<input type="text" value={text} onChange={this.change} />);
}
}
In ECMAScript 2015 classes you need to bind your methods manually.
I don't have time to expand more, because I'm at work, but check this article
http://reactkungfu.com/2015/07/why-and-how-to-bind-methods-in-your-react-component-classes/
Check the ECMAScript 2015 classes section
The sample code is from that post
Regards
bind your call to the App scope:
{ this.showAccount.bind(this) }
should work!

Categories