How can I load the data before the component in ReactJS? - javascript

My purpose for the project is to get the data from Google Analytics API and show all the data as a list. I can get the data from API successfully. I am passing the data to another component. I can see that data is the console but when I am trying to load them into the component I am getting nothing.
My first component look as below:
class TabsExample extends Component {
constructor(props) {
super(props);
this.handleLoad = this.authorize.bind(this);
this.handleProfiles = this.handleProfiles.bind(this);
this.arr = [];
this.state = {
info: [],
details: []
}
}
componentDidMount() {
window.addEventListener('load', this.handleLoad);
}
handleAccounts = (response) => {
var details = response.result.items;
console.log(response)
this.setState({
info: details
});
details.map(x => {
gapi.client.analytics.management.webproperties.list(
{ 'accountId': x.id })
.then(this.handleProperties)
.then(null, function (err) {
console.log(err);
})
})
}
handleProperties = (response) => {
// Handles the response from the webproperties list method.
if (response.result.items && response.result.items.length) {
// Get the first Google Analytics account
var firstAccountId = response.result.items[0].accountId;
// Get the first property ID
var firstPropertyId = response.result.items[0].id;
// Query for Views (Profiles).
this.queryProfiles(firstAccountId, firstPropertyId);
//console.log(firstPropertyId)
} else {
console.log('No properties found for this user.');
}
}
queryProfiles = (accountId, propertyId) => {
// Get a list of all Views (Profiles) for the first property
// of the first Account.
gapi.client.analytics.management.profiles.list({
'accountId': accountId,
'webPropertyId': propertyId
})
.then(this.handleProfiles)
.then(null, (err) => {
// Log any errors.
console.log(err);
})
}
handleProfiles(response) {
// Handles the response from the profiles list method.
if (response.result.items && response.result.items.length) {
// Get the first View (Profile) ID.
var firstProfileId = response.result.items[0].id;
// Query the Core Reporting API.
//console.log(firstProfileId);
//this.queryCoreReportingApi(firstProfileId);
gapi.client.analytics.data.ga.get({
'ids': 'ga:' + firstProfileId,
'start-date': '30daysAgo',
'end-date': 'today',
'metrics': 'ga:sessions, ga:bounces, ga:users'
})
.then((response) => {
// this.setState({
// details: [this.state.details, response]
// })
this.arr.push(response)
})
} else {
console.log('No views (profiles) found for this user.');
}
}
queryCoreReportingApi(profileID) {
console.log(profileID);
}
authorize = (event) => {
var useImmidiate = event ? false : true;
var authData = {
client_id: CLIENT_ID,
scope: SCOPES,
immidiate: useImmidiate
};
gapi.auth.authorize(authData, (response) => {
gapi.client.load('analytics', 'v3').then(() => {
//console.log(response);
gapi.client.analytics.management.accounts.list()
.then(this.handleAccounts);
});
});
}
render() {
return (
<Tabs>
<Tab label="Accounts" >
<div>
<NewsList info={this.arr} />
{this.arr}
</div>
</Tab>
<Tab label="Visual Data" >
<div>
<h2 className='tab_headline'>Tab Two</h2>
<div className="row">
<div className="col-md">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/2e/Pie_chart_EP_election_2004.svg/1280px-Pie_chart_EP_election_2004.svg.png"
alt="First"
className="img-thumbnail"
style={divHeight} />
</div>
<div className="col-md">
<img src="https://confluence.atlassian.com/fisheye/files/298976800/299139805/3/1484820924815/FishEye_Charts_Page02.png"
alt="First"
className="img-thumbnail"
style={divHeight} />
</div>
</div>
</div>
</Tab>
<Tab
label="User Information"
data-route="/home">
<div>
<h2 className='tab_headline'>Tab Three</h2>
</div>
</Tab>
</Tabs>
)
}
}
I am passing all the data to below component:
import React, { Component } from 'react';
class newsList extends Component {
items = (props) => {
if(props){
return props.info.map((prop)=>{
return(
<div>{prop.result.id}</div>
)
})
}
}
render() {
return(
<div>
<ul className="collection">
{console.log(this.state.details)}
</ul>
</div>
)
}
}
export default newsList;
When I see the console log I can see the Array [ ]. At the start it is not having any data. After sometime when I click again on Array [ ] I can see that it is having 2 objects. but I cannot use these objects. How can I do this?

Your current implementation does not utilise your React Component's state.
At the moment, your arr value is simply attached to this, therefore, React cannot see when it changes.
Embedding your arr value within your React component's state will trigger a render() every time it is changed using this.setState(), thereby making your application responsive to changes in it's value.
See below for an example of implementation.
Init:
constructor(props) {
super(props)
this.state: {
arr: []
}
}
Update:
this.setState({
arr: response
})
Retrieve:
const arr = this.state.arr

Related

render method being called before API data is loaded in React

Total beginner with React.
I am trying to work out the standard approach to this situation in React.
I am accessing an api, the data is being returned all ok, except I am trying to set the data as a state of my component, and the render() method is referencing the state before any data is returned so the state property is being defined as 'null'.
In my code sample below you can see I am logging to the console, and despite the order of things, the second log is being returned from the browser before the one that has setState to be the API data.
Any help / explanation as to why this is happening despite using .then() would be appreciated.
Thank you.
PS: I have removed the TeamList component for simplification, but like the 'second log', the component gets rendered before the data has actually been pulled in.
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
}
}
componentDidMount() {
const uri = 'http://api.football-data.org/v2/competitions/PL/teams';
let h = new Headers()
h.append('Accept', 'application/json')
h.append('X-Auth-Token', 'XXXXXXXXXXXXXXXXXXXX')
let req = new Request(uri, {
method: 'GET',
headers: h,
mode: 'cors'
})
var component = this;
fetch(req)
.then( (response) => {
return response.json()
})
.then( (json) => {
this.setState({ data: json })
})
.then( (json) => {
console.log( 'second log', this.state.data )
})
.catch( (ex) => {
console.log('parsing failed', ex)
})
console.log( 'first log', this.state.data )
}
render() {
return (
<div>
<div className="App">
<TeamList list={this.state.data} />
</div>
</div>
);
}
}
export default App;
You need to add something like this to the start of your render():
if (this.state.data === null) {
return false;
}
So your code should be:
render() {
if (this.state.data === null) {
return false;
}
return (
<div>
<div className="App">
<TeamList list={this.state.data} />
</div>
</div>
);
}
render() is called immediately, but you want it to return false until this.state.data has data
When you mount a component, it gets rendered immeadiately with the initial state (that you've set in the constructor). Then later, when you call setState, the state gets updated and the component gets rerendered. Therefore it makes sense to show something like "loading..." until state.data is not null:
render() {
return (
<div>
<div className="App">
{this.state.data ? <TeamList list={this.state.data} /> : "loading..." }
</div>
</div>
);
}
Now additionally logging does not work as expected as setState does not return a promise, so:
.then( (json) => {
this.setState({ data: json })
})
.then( (json) => {
console.log( 'second log', this.state.data )
})
is actually the same as:
.then( (json) => {
this.setState({ data: json })
console.log( 'second log', this.state.data )
})
and that still logs null as setState is asynchronous, which means that calling it does not change this.state now but rather somewhen. To log it correctly use the callback:
then( (json) => {
this.setState({ data: json }, () => {
console.log( 'second log', this.state.data )
});
})
Just an idea:
import React, { Component } from 'react';
class App extends Component {
constructor(props)
{
super(props);
this.state = {
data: null,
};
}
componentDidMount()
{
fetch('http://api.football-data.org/v2/competitions/PL/teams')
.then(response => response.json())
.then(data => this.setState({ data }));
}
render() {
return (
<div>
<div className="App">
<TeamList list={this.state.data} />
</div>
</div>
);
}
}
export default App;
TeamList :
class TeamList extends React.Component {
constructor(props) {
super(props);
}
render(){
return (
<ul>
{
this.props.list.map((element, i) => {
return (
<li className="un-res t_d " key={i}>{element}</li>
)
}
})
}
}
export default TeamList
Happy coding!

Search results not updating after second search - REACT

I set up a search bar, and after I search the results will pop up. However, the issue is, if I don't refresh the page and search again, it will push me to the new search, but the search results won't update with it. Why would the updated param be showing even though the results aren't updating?
Ex. first url is search/erl,second url is search/Groovy%20Playlist
First search
Second search, query param updated, but search results didn't
Searchbar.js
class SearchBar extends Component {
constructor(props) {
super(props)
this.state = {query: '', results: [], isLoading: false}
}
componentWillMount() {
this.resetComponent()
}
resetComponent = () => this.setState({ isLoading: false, results: [], query: '' })
search(query) {
this.setState({ query });
axios
.get(`/api/search?query=${query}`)
.then(response => {
this.setState({ results: response.data});
})
.catch(error => console.log(error));
}
handleFormSubmit = () => {
console.log('search:', this.state.query);
this.props.action
this.props.history.push(`/search/${this.state.query}`)
this.resetComponent()
}
handleInputChange = (query) => {
this.search(query);
this.setState({ isLoading: true, query })
setTimeout(() =>
this.setState({
isLoading: false,
}) , 300)
}
handleResultSelect = (e, { result }) => this.setState({ query: result.title} )
render () {
const resultRenderer = ({ title }) => <List content = {title}/>
return (
<Form onSubmit={this.handleFormSubmit}>
<Search
loading={this.state.isLoading}
onResultSelect={this.handleResultSelect}
onSearchChange={(event) => {this.handleInputChange(event.target.value)}}
showNoResults={false}
value={this.state.query}
resultRenderer={resultRenderer}
results ={this.state.results}
type={"submit"}
{ ...this.props} />
</Form>
);
}
}
export default withRouter (SearchBar)
Search.js
class Search extends Component {
constructor(props) {
super(props)
this.state = {
results: []
}
}
componentWillMount() {
const { match: { params } } = this.props;
axios
.get(`/api/search?query=${params.query}`)
.then(response => {
console.log(response);
this.setState({ results: response.data });
})
.catch(error => console.log(error));
}
render() {
console.log(this.state.results)
return(
<div>
<div className = "heading centered">
<h1> Search results for: {this.props.match.params.query} </h1>
</div>
{this.state.results.map((post) => {
return(
<Post key = {post.id} post={post}/>
)
})}
</div>
);
}
}
export default Search
Updating results of the SearchBars state will be passed down to Search's props, but you don't work with this.props.results but rather with this.state.results, and that doesnt get updated even if the props change. That works the first time as you reload the Search's state inside componentWillMount but that doesnt get called again as the component is not remounted. Therefore Search always works with its states results, that are never updated.
Now to solve this chaos, remove the componentWillMount logic from Search as that is actually doing what SearchBar already does, and add a listener to componentWillReceiveProps that updates the Searches state, or don't work with the state at all inside Search but take the passed in results instead as this.props.results.
const Search = ({ match, results }) => (
<div>
<div className = "heading centered">
<h1> Search results for: {match.params.query} </h1>
</div>
{results.map((post) =>
<Post key = {post.id} post={post}/>
)}
</div>
);

React 'this' undefined when adding table row

I'm attempting to add a new row of data to a table that occurs when a user inputs text into a field and clicks a button.
The button click is tied to a function (AddNewRow) which sends the data to a controller and then adds a new row to the table with the data.
The data is sent to the controller correctly and if the page is refreshed the new row is showing (because of the get request after mount) but the problem is the table doesn't update dynamically.
I keep getting a console error saying 'this is undefined' in the AddNewRow function.
Ive attempted to bind 'this' to the constructor by using both '.bind(this)' and AddNewRow() => {} but it still doesn't bind?
class App extends React.Component {
constructor() {
super();
this.state = {
tableData: [{
}],
};
}
componentDidMount() {
axios.get('/Jobs/GetJobs', {
responseType: 'json'
}).then(response => {
this.setState({ tableData: response });
});
}
AddNewRow(){
axios.post('/Controller/CreateJob', { Name: this.refs.NewJobName.value})
.then(function (response){
if(response.data.Error) {
window.alert(response);
}
else {
var data = this.setState.tableData;
this.setState.tableData.push(response);
this.setState({ tableData: data });
}
})}
render() {
const { tableData } = this.state;
return (
<div>
<button onClick={() => this.AddNewRow()} >ADD</button>
<input ref="NewJobName" type="text" placeholder="Name" />
<ReactTable
data={tableData}
/>
</div>
)
}
Use arrow function to make this available in the then function:
axios
.post('/Controller/CreateJob', { Name: this.refs.NewJobName.value })
.then((response) => {
if (response.data.Error) {
window.alert(response);
} else {
this.setState(prevState => ({
tableData: prevState.tableData.concat([response])
}));
}
});

javascript/ReactJS: Show results from backend in a list

I am sending a GET request on a Node API with a MongoDB server. I am getting the response as JSON in an array of object format. I want to show all those results in a list. Right now i am making a function like this
class VendorDashboard extends React.Component {
constructor() {
super();
this.state = {
paginationValue: '86',
title: ""
}
this.handleLogout = this.handleLogout.bind(this);
this.gotoCourse = this.gotoCourse.bind(this);
}
componentDidMount() {
axios.get('/vendor/showcourses') //the api to hit request
.then((response) => {
console.log(response);
let course = [];
course = response.data.map((courseres) => {
this.setState({
title: courseres.title
});
})
});
Right now what is happening is it is showing just one result. I want to show all results on that api. How can i do it?
This segment here is overriding the title per course.
course = response.data.map((courseres) => {
this.setState({
title: courseres.title
});
})
You can keep the state as an array of titles and do;
course = response.data.map((courseres) => {
return courseres.title;
})
this.setState({titles: course});
And then you can repeat on the array of titles in your component.
Like so in the render method;
const { titles } = this.state;
return <div>{titles.map((title, index) => <div key={index}>{title}</div>)}</div>
You need to collect all the server response and set that as an array of data to the state and use this state data to render:
class VendorDashboard extends React.Component {
constructor() {
super();
this.state = {
paginationValue: '86',
course: []
}
this.handleLogout = this.handleLogout.bind(this);
this.gotoCourse = this.gotoCourse.bind(this);
}
componentDidMount() {
axios.get('/vendor/showcourses') //the api to hit request
.then((response) => {
const course = response.data.map((courseres) => ({
id: courseres.id,
title: courseres.title
}));
this.setState({
course
});
});
}
render() {
return (
<ul>
{
this.state.course.map((eachCourse) => {
return <li key={eachCourse.id}>{eachCourse.title}</li>
})
}
</ul>
)
}
}
In each map iteration you rewrite your piece of state, it is wrong.
Just put courses in your state:
console.log(response);
this.setState({ courses: response.data });
In render method go through your state.courses:
render(){
return(
<div>
{this.state.courses.map(course => <h2>{course.title}</h2>)}
</div>
);
}

React setState inside solidity contract instance promises

I deployed a solidity contract to my local testrpc blockchain. All my contract method tests check out, but handling Web3 transactions and updating state accordingly is giving me trouble.
When I add a user account, my next operation is to return all user accounts for my contract. and well...update my state (RegisteredAccounts).
However, through my chain of promises I'm not seeing my states update. I understand setState is asynchronous too, so how can I see my states update without refreshing the page or calling ComponentDidMount()?
Here is my Solidity Accounts Contract (the parts that I've handled so far
pragma solidity ^ 0.4.4;
contract Accounts {
mapping(address => User) public mUsers;
address[] public Users; //users whitepages
struct User {
string handle;
bytes32[] taskList;
}
function addNewUser(string _handle) returns(bool success) {
address newUserAddr = msg.sender;
//if handle not in userAddresses & the handle is not null
if (bytes(mUsers[newUserAddr].handle).length == 0 && bytes(_handle).length != 0) {
mUsers[newUserAddr].handle = _handle;
Users.push(newUserAddr);
return true;
} else {
return false;
}
}
function getUsers() constant returns(address[]) {
return Users;
}
}
Here is my App container component -- relevant parts
registerNewUser() is my problem child right now.
class App extends Component {
state = {
modalOpen: false,
SenderAddress: null,
RegisteredAccounts: [],
isRegisteredUser: false,
SenderTaskList: [], //not set
AccountsCtrct: null,
web3: null
}
//#region APP METHODS
componentWillMount() {
// Get network provider and web3 instance. -- See utils/getWeb3 for more info.
getWeb3.then(results => {
this.setState({
web3: results.web3
})
this.instantiateContracts() //instantiate contract
}).catch(() => {
console.log('Error finding web3.')
})
}
instantiateContracts() {
this.setState({
AccountsCtrct: contract(AccountsContract)
})
this.state.AccountsCtrct.setProvider(this.state.web3.currentProvider)
//Get block chain addresses --- only returns the current address selected in metamask (web3 current addr)
this.state.web3.eth.getAccounts((error, accounts) => {
this.setState({
SenderAddress: accounts[0]
})
//INIT ACCOUNTS CONTRACT
var acctDeployed = this.state.AccountsCtrct.deployed()
acctDeployed.then((instance) => {
return instance.getUsers();
}).then((res) => {
this.setState({
RegisteredAccounts: res
})
if (this.state.RegisteredAccounts.includes(this.state.SenderAddress)) {
this.setState({
isRegisteredUser: true
})
}
})
})
}
registerUser = (handle) => {
var acctInstance
this.state.AccountsCtrct.deployed().then((inst) => {
//add current user to this account
acctInstance = inst
return acctInstance.addNewUser(handle, {
from: this.state.SenderAddress
});
}).then(() => {
//now we added our user -- update registeredAccounts setState
//pass response users array to promise
return acctInstance.getUsers()
}).then(res => {
this.setState({
RegisteredAccounts: res
})
if (res.includes(this.state.SenderAddress)) {
this.setState({
isRegisteredUser: true
})
}
})
}
toggleModal = () => {
this.setState(prevState => ({
modalOpen: !prevState.modalOpen
}));
}
//#endregion
render() {
return (
<div className="App">
<nav className="navbar pure-menu pure-menu-horizontal">
Truffle Box
{
!this.state.isRegisteredUser
? <a style={navLink} onClick={ this.toggleModal } href="#" className="pure-menu-heading pure-menu-link">Register</a>
: null
}
</nav>
<ModalUserNav visible={this.state.modalOpen}
toggleModal={this.toggleModal}
isRegistered={this.state.isRegisteredUser}
registerUser={this.registerUser} />
);
}
}
Last my Child component
class ModalUserNav extends Component {
state = {
unpl: "UserName",
pwpl: "Password",
errorCode: 'Registration Failed',
errorVisible: false
}
handleOnChangePL = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
handleSubmit = () => {
if (this.state.unpl !== "") {
this.props.registerUser(this.state.unpl)
this.props.toggleModal();
} else {
//if the input is empty update the error code and show
console.log('registration failed!')
this.setState({
errorCode: 'REGISTRATION ERR: empty handles are not allowed!',
errorVisible: true
})
}
}
render() {
return (
<section>
<Modal visible={this.props.visible} effect="fadeInUp">
<div className="pure-form">
<fieldset style={modalFormView}>
<legend style={{fontSize: "18px"}}><b>Register now. All you need is a handle!</b></legend>
<div className="flexContainer">
<input style={{marginTop: "7px", height: "2.6em", marginLeft: "5px", marginRight: "5px"}} type="text" name="unpl" placeholder={this.state.unpl} onChange={(event) => {this.handleOnChangePL(event)}} value={this.state.unpl} />
<button style={btnStyle} type="submit" className="pure-button pure-button-primary" onClick={() => {this.handleSubmit()}}><b>Register</b></button>
</div>
</fieldset>
</div>
</Modal>
</section>
)
}
}
In short, I want to follow up my 2 asynchronous tasks (addNewUser, getUsers) with a setState so I can automatically change my UI without refreshing. So what am I doing wrong?
You should move instantiateContracts to setState because setState does not update data immediately. https://reactjs.org/docs/react-component.html#setstate
this.setState({
web3: results.web3
}, () => {
this.instantiateContracts() //instantiate contract
})
Update 1: About registerUser: It should be
this.setState({
RegisteredAccounts: res
}, () => {
if (res.includes(this.state.SenderAddress)) {
this.setState({
isRegisteredUser: true
})
}
})

Categories

Resources