React 'this' undefined when adding table row - javascript

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])
}));
}
});

Related

React: Form Submit how to pass multiple row data?

I am getting the data from api. I am displaying Feature ID, DisplayOrder textbox in the rows. User can change the Display Order value in the multiple rows. How to update the information using Post API? I am passing one value FeatureID and DisplayOrder in form submit. Please help to pass all the values that are changed(FeatureID, DisplayOrder) in form submit. If suppose FeatureID 11 and FeatureID 13 Display order changes, then form submit needs to pass these information only.
{"FeatureID":"11","DescriptionText":"Travel","FeatureText":Feature2,"DisplayOrder":"1","Date":"08/30/2011","FeatureName":"Research"},
{"FeatureID":"12","DescriptionText":"Sport","FeatureText":Feature3,"DisplayOrder":"2","Date":"08/30/2011","FeatureName":"Research"},
{"FeatureID":"13","DescriptionText":"Art","FeatureText":Feature4,"DisplayOrder":"3","Date":"08/30/2011","FeatureName":"Research"}]
import React from "react";
export class EditFeatures extends React.Component {
constructor(props) {
super(props);
this.state = {
FeatureID: "",
DisplayOrder: "",
DescriptionText: "",
FeatureText: "",
Feature: [],
};
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.DisplayFeatures();
}
DisplayFeatures() {
fetch(REQUEST_URL, { "Content-Type": "application/xml; charset=utf-8" })
.then((response) => response.json())
.then((data) => {
this.setState({
Feature: data,
loading: false,
});
});
}
handleSubmit(event) {
event.preventDefault();
const FeatureID = this.state.FeatureID;
const DisplayOrder = this.state.DisplayOrder;
const data = {
FeatureID,
DisplayOrder,
};
fetch(REQUEST_URL, {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
})
.then((response) => response.json())
.catch((error) => console.error("Error:", error))
.then((response) => console.log("Success", data));
window.location.href = "/";
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<table>
<tbody>
{this.state.Feature.map((item, index) => {
return [
<tr key={item.FeatureID}>
<td>
<input
type="text"
id={item.FeatureID}
name="DisplayOrder"
value={item.DisplayOrder}
onChange={(ev) => {
const newFeature = this.state.Feature.map((f) => {
if (f.FeatureID == ev.target.id) {
f.DisplayOrder = ev.target.value;
}
return f;
});
this.setState({ Feature: newFeature });
}}
/>
</td>
<td>{item.DescriptionText}</td>
<td>{item.FeatureTex}</td>
</tr>,
];
})}
</tbody>
</table>
<button type="submit" name="submit">
Update
</button>
</form>
</div>
);
}
}
export default Edit_Features;
The answer is simple, just sort Feature array on DisplayOrder in handleSubmit like this:
import React from "react";
export class EditFeatures extends React.Component {
constructor(props) {
super(props);
this.state = {
FeatureID: "",
DisplayOrder: "",
DescriptionText: "",
FeatureText: "",
Feature: [],
};
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.DisplayFeatures();
}
DisplayFeatures() {
fetch(REQUEST_URL, { "Content-Type": "application/xml; charset=utf-8" })
.then((response) => response.json()) // you passed Content-Type: "application/xml" as request header but here you use response.json, remove Content-Type header if server API returns json
.then((data) => {
this.setState({
Feature: data.map((feature) => ({ ...feature, changed: false })),
loading: false,
});
});
}
handleSubmit(event) {
event.preventDefault();
const FeatureID = this.state.FeatureID;
const DisplayOrder = this.state.DisplayOrder;
const Feature = this.state.Feature;
const data = {
FeatureID,
DisplayOrder,
Feature, // this is how you pass an array to server, how will the server deserialize this depends on the framework used there
};
const self = this;
fetch(REQUEST_URL, {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
})
.then((response) => response.json())
.catch((error) => console.error("Error:", error))
.then((response) => {
/**
* sort manipulates the array so we clone the Feature array before sorting it
* we pass comparator function to sort so that we sort on DisplayOrder
*/
const newFeature = [...this.state.Feature];
newFeature.sort((f1, f2) => f2.DisplayOrder - f1.DisplayOrder);
self.setState({ Feature: newFeature });
});
window.location.href = "/"; // ok why does this exist?!!
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<table>
<tbody>
{this.state.Feature.map((item) => {
return [
<tr key={item.FeatureID}>
<td>
<input
type="text"
id={item.FeatureID}
name="DisplayOrder"
value={item.DisplayOrder}
onChange={(ev) => {
// this is the proper way to update an element inside an array
const newFeature = [...this.state.Feature];
// I prefer === over == to avoid errors
const featureIndex = newFeature.findIndex(
(f) => f.FeatureID === ev.target.id
);
newFeature[featureIndex].DisplayOrder =
ev.target.value;
this.setState({ Feature: newFeature });
}}
/>
</td>
<td>{item.DescriptionText}</td>
<td>{item.FeatureTex}</td>
</tr>,
];
})}
</tbody>
</table>
<button type="submit" name="submit">
Update
</button>
</form>
</div>
);
}
}
export default EditFeatures;
this way when you click button submit, if the POST request to the server succeeds, the table will be updated according to DisplayOrder.
Note
If the request to the server fails for any reason the table won't be updated, if you don't care about the response of the server just sort the Feature array outside the .then before issuing the request.

setState updates state and triggers render but I still don't see it in view

I have a simple word/definition app in React. There is an edit box that pops up to change definition when a user clicks on "edit". The new definition provided is updated in the state when I call getGlossary(), I see the new definition in inspector and a console.log statement in my App render() function triggers too. Unfortunately, I still have to refresh the page in order for the new definition to be seen on screen. I would think that calling set state for this.state.glossary in the App would trigger a re-render down to GlossaryList and then to GlossaryItem to update it's definition but I'm not seeing it :(.
App.js
class App extends React.Component {
constructor() {
super();
this.state = {
glossary: [],
searchTerm: '',
}
this.getGlossary = this.getGlossary.bind(this); //not really necessary?
this.handleSearchChange = this.handleSearchChange.bind(this);
this.handleAddGlossaryItem = this.handleAddGlossaryItem.bind(this);
this.handleDeleteGlossaryItem = this.handleDeleteGlossaryItem.bind(this);
//this.handleUpdateGlossaryDefinition = this.handleUpdateGlossaryDefinition.bind(this);
}
getGlossary = () => {
console.log('getGlossary fired');
axios.get('/words').then((response) => {
const glossary = response.data;
console.log('1: ' + JSON.stringify(this.state.glossary));
this.setState({ glossary }, () => {
console.log('2: ' + JSON.stringify(this.state.glossary));
});
})
}
componentDidMount = () => {
//console.log('mounted')
this.getGlossary();
}
handleSearchChange = (searchTerm) => {
this.setState({ searchTerm });
}
handleAddGlossaryItem = (glossaryItemToAdd) => {
//console.log(glossaryItemToAdd);
axios.post('/words', glossaryItemToAdd).then(() => {
this.getGlossary();
});
}
handleDeleteGlossaryItem = (glossaryItemId) => {
console.log('id to delete: ' + glossaryItemId);
axios.delete('/words', {
data: { glossaryItemId },
}).then(() => {
this.getGlossary();
});
}
render() {
console.log('render app fired');
const filteredGlossary = this.state.glossary.filter((glossaryItem) => {
return glossaryItem.word.toLowerCase().includes(this.state.searchTerm.toLowerCase());
});
return (
<div>
<div className="main-grid-layout">
<div className="form-left">
<SearchBox handleSearchChange={this.handleSearchChange} />
<AddWord handleAddGlossaryItem={this.handleAddGlossaryItem} />
</div>
<GlossaryList
glossary={filteredGlossary}
handleDeleteGlossaryItem={this.handleDeleteGlossaryItem}
getGlossary={this.getGlossary}
//handleUpdateGlossaryDefinition={this.handleUpdateGlossaryDefinition}
/>
</div>
</div>
);
}
}
export default App;
GlossaryItem.jsx
import React from 'react';
import EditWord from './EditWord.jsx';
const axios = require('axios');
class GlossaryItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isInEditMode: false,
}
this.glossaryItem = this.props.glossaryItem;
this.handleDeleteGlossaryItem = this.props.handleDeleteGlossaryItem;
this.handleUpdateGlossaryDefinition = this.handleUpdateGlossaryDefinition.bind(this);
this.handleEditClick = this.handleEditClick.bind(this);
}
handleUpdateGlossaryDefinition = (updateObj) => {
console.log('update object: ' + JSON.stringify(updateObj));
axios.put('/words', {
data: updateObj,
}).then(() => {
this.props.getGlossary();
}).then(() => {
this.setState({ isInEditMode: !this.state.isInEditMode });
//window.location.reload();
});
}
handleEditClick = () => {
// display edit fields
this.setState({ isInEditMode: !this.state.isInEditMode });
// pass const name = new type(arguments); data up to App to handle with db
}
render() {
return (
<div className="glossary-wrapper">
<div className="glossary-item">
<p>{this.glossaryItem.word}</p>
<p>{this.glossaryItem.definition}</p>
<a onClick={this.handleEditClick}>{!this.state.isInEditMode ? 'edit' : 'cancel'}</a>
<a onClick={() => this.handleDeleteGlossaryItem(this.glossaryItem._id)}>delete</a>
</div>
{this.state.isInEditMode ?
<EditWord
id={this.glossaryItem._id}
handleUpdateGlossaryDefinition={this.handleUpdateGlossaryDefinition}
/> : null}
</div>
);
}
}
EditWord
import React from 'react';
class EditWord extends React.Component {
constructor(props) {
super(props);
this.state = {
definition: ''
};
this.handleUpdateGlossaryDefinition = this.props.handleUpdateGlossaryDefinition;
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
let definition = event.target.value;
this.setState({ definition });
}
handleSubmit(event) {
//console.log(event.target[0].value);
let definition = event.target[0].value;
let update = {
'id': this.props.id,
'definition': definition,
}
//console.log(update);
this.handleUpdateGlossaryDefinition(update);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit} className="glossary-item">
<div></div>
<input type="text" name="definition" placeholder='New definition' value={this.state.definition} onChange={this.handleChange} />
<input type="submit" name="update" value="Update" />
</form>
);
}
}
export default EditWord;
Thank you
One possible way I can see to fix this is to map the data to make the id uniquely identify each list item (even in case of update). We can to do this in getGlossary() by modifying the _id to _id + definition.
getGlossary = () => {
console.log('getGlossary fired');
axios.get('/words').then((response) => {
// Map glossary to uniquely identify each list item
const glossary = response.data.map(d => {
return {
...d,
_id: d._id + d.definition,
}
});
console.log('1: ' + JSON.stringify(this.state.glossary));
this.setState({ glossary }, () => {
console.log('2: ' + JSON.stringify(this.state.glossary));
});
})
}
In the constructor of GlossaryItem I set
this.glossaryItem = this.props.glossaryItem;
because I am lazy and didn't want to have to write the word 'props' in the component. Turns out this made react loose reference somehow.
If I just remove this line of code and change all references to this.glossaryItem.xxx to this.pros.glossaryItem.xxx then it works as I expect! On another note, the line of code can be moved into the render function (instead of the constructor) and that works too, but have to make sure I'm accessing variables properly in the other functions outside render.

API call in react from user input

I am trying to build a simple weather application. I need to get an input from the user and then submit it. The following code below doesn't work as required. Can someone please help as I am beginner
class App extends Component {
state = {
search: ''
}
inputSubmitHandler = (e) => {
this.setState({
search: e.target.value
})
}
render() {
return (
<div>
<form >
<input onChange={this.inputSubmitHandler}/>
<button type='submit'>Submit</button>
</form>
<Weather search={this.state.search}/>
</div>
)
}
}
I want to get the full input only after the user clicks submit, and then change the state and then pass it to the Weather component.
Edit: Here is the Weather component to make things more clear
class Weather extends Component {
state = {
temp: null,
humidity: null,
}
Getweather = (search) => {
axios.get('https://cors-anywhere.herokuapp.com/http://api.weatherapi.com/v1/current.json?key=d0c1e9b30aef451789b152051200907&q='+search)
.then(res => {
const tempr = res.data.current.temp_c;
const humidity = res.data.current.humidity;
this.setState({
temp: tempr,
humidity: humidity,
})
// console.log(res);
})
}
render() {
this.Getweather(this.props.search)
return (
<div>
{this.state.temp}
{this.state.humidity}
</div>
)
}
}
If i understand you correctly, you need two different variables: (1) the input variable (search) and then a buffered variable which is filled when the button is clicked:
class App extends Component {
constructor() {
this.state = { search: '', clippedSearch: null }
}
onChange(ev) {
this.setState({ search: ev.target.value })
}
onSubmit(ev) {
this.setState({ clippedSearch: this.state.search })
}
render() {
return <div>
<form>
<input type="text" value={this.state.search} onChange={onChange} />
<button onClick={onSubmit}>Submit</button>
<Weather search={this.state.clippedSearch} />
</form>
</div>
}
}
Your this code is fine:
handleChange = (e) => {
this.setState({ search: e.target.value }); // This will update the input value in state
}
and then you can try:
<form onSubmit={this.submitHandler}>
and define the submitHandler like:
submitHandler = (e) => {
e.preventDefault(); // It will hold the form submit
console.log('state:', this.state.search);
// You will get the updated state ( the one that yo have updated on onChange event listener ) here, make your api call here with the updated state
}
Issue:
<Weather search={this.state.search}/>
Here you are passing the state instantly and that's why it starts sending requests continuously.

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>
);
}

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

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

Categories

Resources