why is this.state.records.amount undefined? - reactjs

I am in the process of learning React and having some trouble with my state. I am trying to get a function to log this.state.records.amount to my console when the component is rendered but it shows up as undefined. If someone can figure this out it would be VERY much appreciated.
Records component:
class Records extends React.Component {
constructor(props) {
super(props);
this.state = {
records: []
}
this.handleNewRecord = this.handleNewRecord.bind(this);
this.handleDeleteRecord = this.handleDeleteRecord.bind(this);
this.surplus = this.surplus.bind(this);
this.debt = this.debt.bind(this);
this.total = this.total.bind(this);
}
componentDidMount() {
this.setState({
records: this.props.records
})
}
handleNewRecord(record) {
let records = this.state.records.slice();
records.push(record)
this.setState({
records: records
})
}
handleDeleteRecord(record) {
let records = this.state.records.slice();
let index = records.indexOf(record)
records.splice(index, 1)
this.setState({
records: records
})
}
surplus() {
console.log(this.state.records[0].amount)
}
debt() {
console.log("debt")
}
total() {
console.log("total")
}
render() {
const records = this.state.records.map((record) =>
<Record record={record} key={record.id} handleDeleteRecord={this.handleDeleteRecord}/>
)
return (
<div className="container">
<h1>Records</h1>
<div className="row">
<AmountBox panelClass="panel panel-primary" panelHeader="Surplus" calculatedAmount={this.surplus()} />
<AmountBox panelClass="panel panel-warning" panelHeader="Debt" calculatedAmount={this.debt()} />
<AmountBox panelClass="panel panel-success" panelHeader="Total" calculatedAmount={this.total()} />
</div>
<RecordForm handleNewRecord={this.handleNewRecord}/>
<table className="table">
<thead>
<tr>
<th>Date</th>
<th>Title</th>
<th>Amount</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{records}
</tbody>
</table>
</div>
)
}
}
Amount Box component:
class AmountBox extends React.Component {
constructor(props) {
super(props);
}
render () {
return (
<div className="col-md-4">
<div className={this.props.panelClass}>
<div className="panel-heading">
<h3 className="panel-title">{this.props.panelHeader}</h3>
</div>
<div className="panel-body">
<p>
{this.props.calculatedAmount}
</p>
</div>
</div>
</div>
)
}
}

this.state.records[0].amount is undefined because on first render you are setting records to [] (in the constructor).
setState will trigger a second render, but in the first render the changes to state by setState will not apply.
So, you need some defensive code that makes sure that this.state.records have items.
surplus() {
this.state.records.length ? this.state.records[0].amount : 0;
}

Related

TypeError: this.state.edit is not a function

I want to create update operation on ReactJS
first I set edit button as
export default class Viewcustomer extends React.Component{
constructor(props) {
super(props)
this.state = {
customers:[]
}
}
componentDidMount() { /* lifecycle method*/
axios.get(`http://localhost:5001/customers/customerView`)
.then(res => {
const customers = res.data;
this.setState({customers});
})
}
onChange = (e) => {
this.setState(
{[e.target.name]: e.target.value}
)
}
edit=personId=>{
console.log(personId);
}
render(){
return(
<div>
<br/><br/>
<div className='container' style={container}>
<h1 style={h1}>Customer Details</h1>
<div className='col-md-12' style={colmd12}>
<br/><br/>
<div className="tbl-header" style={tblheader}>
<table className="table" style ={table} >
<thead className='thead' >
<tr className='tr' >
<th >Id</th>
<th>name</th>
<th>NIC</th>
<th>type</th>
<th>Delete</th>
<th>Update</th>
</tr>
</thead>
</table>
</div>
<div className="tbl-content" style={tblcontent}>
<table className="table" style ={table} >
<tbody>
{ this.state.customers.map(person =>
<tr className='td' style={td}>
<td>{person.Id}</td>
<td>{person.name}</td>
<td>{person.NIC}</td>
<td>{person.type}</td>
<td><Link to="update"><i class="fa fa-trash-o" style={iconstyle}></i></Link></td>
<td><Link to="update"><i class="fa fa-file" style={iconstyle} onClick={()=>this.state.edit(person.Id)}></i></Link></td>
</tr>)}
</tbody>
</table>
</div>
</div>
</div>
</div>
)
}
}
the update icon routes to Updatecustomer.jsx file.
then I set the edit fuction on Update.jsx file
import React from "react";
export default class Updatecustomer extends React.Component{
constructor(props){
super(props)
this.state={
update:[]
}
}
onChange = (e) => {
this.setState(
{[e.target.name]: e.target.value}
)
}
edit=personId=>{
console.log(personId);
}
render(){
return(
<div>
</div>
)
}
}
then my browser gives the following error: (TypeError: this.state.edit is not a function)
It is very big help if you have some ideas to fix this.
The edit function is not part of your state. Use onClick={()=>this.edit(person.Id)} instead.
Just remove state part and try
this.edit(person.id)

Get child components to talk to eachother

I know this has been asked before however I've been playing around with this for awhile and I'm not getting it. I have two child components being used and connected by parent page. The first child component is a form and the second child component is a table containing a list of all the userProfiles. When I submit the form, I wand to tell the table component to run a function. How do I do this?
Path: Parent Component
export default class StudentPage extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<div>
<AddStudentForm
guardianUserProfiles={this.props.guardianUserProfiles}
/>
<StudentTable
studentUserProfiles={this.props.studentUserProfiles}
/>
</div>
);
}
}
Path: AddStudentForm
export default class AddStudentForm extends React.Component {
constructor(props) {
super(props);
this.state = {
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
}
render() {
return (
<Form onSubmit={this.handleSubmit} className="mb-3">
<Input
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.handleInputChange}
id="firstName"
placeholder="First name"
/>
<button type="submit" className="btn btn-primary">Save</button>
</Form>
);
}
}
Path: StudentTable
export default class StudentTable extends React.Component {
constructor(props) {
super(props);
this.state = {
};
this.handleTableUpdate = this.handleTableUpdate.bind(this);
}
handleTableUpdate = () => (event) => {
// Do stuff
}
render() {
return (
<div>
<Table hover bordered responsive>
<thead>
<tr>
<th className="border-left border-top-0 border-right-0 pt-0">#</th>
<th className="border-left-0 border-top-0 border-right-0 pt-0">First name</th>
</tr>
</thead>
<tbody>
{this.state.userProfile.map((studentUserProfile, idx) => (
<tr>
<React.Fragment>
<th rowSpan={studentUserProfile.classes.length} className="border-left aling-double-row">{idx + 1}</th>
<td rowSpan={studentUserProfile.classes.length} className="aling-double-row">{studentUserProfile.firstName}</td>
</React.Fragment>
</tr>
))}
</tbody>
</Table>
</div>
);
}
}
Way 1:
You can achieve this either by using flux or redux
Way 2:
You can send a call back form child1(ie page with form) to parent and send it as a prop from parent to child2(ie page with table).

Passing data and events between siblings in React

I'm trying to pass data from a search component to a result component. The idea is that the input from the search field in the search component will be sent to the result component and used as a parameter for an API-call when the search button is clicked.
I've based the data-flow structure on this article: https://codeburst.io/no-redux-strategy-for-siblings-communication-3db543538959, but I'm new to React and it's a bit confusing.
I tried using the parameter by directly getting it from props like so vinmonopolet.searchProducts({this.props.data}, but I got a syntax error.
I'm also unclear on how one would go about directing onClick events from one component to another.
Parent
class App extends Component {
constructor(){
super();
this.state = { data: '' }
}
fromSearch(param){
this.setState({
data: param
});
}
render() {
return (
<div className="App">
<Navbar />
<Searchbar callback={this.fromSearch.bind(this)} />
<ResultTable data={this.state.data}/>
</div>
);
}
}
Child1
class Searchbar extends React.Component{
getContent(event){
this.props.callback(event.target.value);
}
Searchbar.protoTypes = {
callback: ProtoTypes.func
}
render(){
return(
<div className="search-container">
<div className="search-field">
<input type="text" placeholder="Hva leter du etter?"
onChange={this.getContent.bind(this)}/>
<button type="button" onClick={???}>Search</button>
</div>
...
Child2
class ResultTable extends React.Component{
constructor(){
super();
this.state = {products: []}
}
searchAllProducts(){
const vinmonopolet = require('vinmonopolet')
vinmonopolet.searchProducts({this.props.data}, {sort: ['price', 'asc']}).then(response => {
const data = response.products;
const listItems = data.map((d) =>
<tr key={d.name}>
<td>{d.productType}</td>
<td>{d.name}</td>
<td>{d.price}kr</td>
</tr>
);
this.setState({products: listItems});
})
}
render(){
if(!this.state.products) return <p>Henter data...</p>;
return(
<div className="result-container">
<div className="result-table-header">
<table>
<th>Type</th>
<th>Varenavn</th>
<th>Pris</th>
</table>
</div>
<div className="result-table-body">
<table className="result-table">
{this.state.products}
</table>
</div>
</div>
);
}
}

Raised Button onClick triggers different function

I have two components, RaisedButton and TableList. Tablelist return select rows and the same is updated in the state (currentSelectedRows). RaisedButton simply consoles the currentSelectedRows. Now the problem statement:
onClick of RaisedButton it consoles the state properly (using approveSelected) till the time updateSelectedRows does not update the state. Once the state is updated inside updateSelectedRows method, onClick of RaisedButton component first calls updateSelectedRows then approveSelected. Below is the code.
export default class MyList extends React.Component {
constructor(props) {
super(props);
this.state = {
pendingList:[],
currentSelectedRows:[]
}
}updateSelectedRows(selectedRows){
console.log("updateCalled");
this.setState({
currentSelectedRows:selectedRows
});
};
approveSelected() {
console.log("approve selected");
console.log(this.state.currentSelectedRows);
};
render() {
return (
<div className="container">
<div className="row_align_right">
<RaisedButton label="APPROVE" backgroundColor={MUIColors.lightGreen500} labelColor={MUIColors.white} onClick={this.approveSelected.bind(this)} />
</div>
<div className="content">
<div className="">
<TableList
selectedRows={this.state.currentSelectedRows}
updateSelectedRows={this.updateSelectedRows.bind(this)}
/>
</div>
</div>
</div>
)
}
}
Any advice would be of great help.
Thanks
You didn't provide the code for TableList so it's hard to know what could be the problem but it seems to work when you just pass a row id upwards to the parent:
const usersList = [
{ name: 'John', age: 33 },
{ name: 'Jane', age: 32 },
{ name: 'David', age: 28 },
{ name: 'Eve', age: 29 },
];
class Row extends React.Component {
onClick = e => {
const { onClick, rowId } = this.props;
onClick(rowId)
}
render() {
const { user } = this.props;
return (
<tr onClick={this.onClick}>
<td>{user.name}</td>
<td>{user.age}</td>
</tr>
);
}
}
class TableList extends React.Component {
onClick = rowId => {
this.props.updateSelectedRows(rowId);
}
render() {
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
{
usersList.map((user, i) => <Row key={i} rowId={i} user={user} onClick={this.onClick}/>)
}
</tbody>
</table>
);
}
}
class MyList extends React.Component {
constructor(props) {
super(props);
this.state = {
pendingList: [],
currentSelectedRows: []
}
} updateSelectedRows(selectedRows) {
console.log("updateCalled");
this.setState({
currentSelectedRows: selectedRows
});
};
approveSelected() {
console.log("approve selected");
console.log(this.state.currentSelectedRows);
};
render() {
return (
<div className="container">
<div className="row_align_right">
<button onClick={this.approveSelected.bind(this)}>Click</button>
</div>
<div className="content">
<div className="">
<TableList
selectedRows={this.state.currentSelectedRows}
updateSelectedRows={this.updateSelectedRows.bind(this)}
/>
</div>
</div>
</div>
)
}
}
ReactDOM.render(<MyList />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

React JS assign separate onclick event to every row of table

I have a table which have a setting icon as the last column and whenever a user clicks on it, it should pop open a setting menu. To toggle between active class I used state and passed it to the array.map function, but what is happening is whenever a user clicks on one setting icon all the menus open simultaneously and the reason is they all have same click event and same state variable. How can I change it to where only the clicked setting icon should have its menu opened? My code is given below.
import React, { Component, PropTypes } from 'react';
import '../../../assets/custom_css/tables/unstackable_very_basic_striped_users_table.css';
import { v4 } from 'node-uuid';
import Language from '../../../assets/language';
class UnstackableVeryBasicStripedUsersTable extends Component {
static propTypes = {
rows: PropTypes.array.isRequired
};
constructor(props) {
super(props);
this.getTableRows = this.getTableRows.bind(this);
this.open_setting_menu = this.open_setting_menu.bind(this);
this.state = {
is_action_menu_active: false
};
}
getTableRows() {
const { rows } = this.props;
return rows.map(row => {
let drop_down_class = (this.state.is_action_menu_active) ? "active" : "";
let menu_class = (this.state.is_action_menu_active) ? "transition visible" : "";
return <tr key={v4()}>
{row.map(info => {
return <td key={v4()}>
{info}
</td>
})}
<td>
<div className={"ui right pointing dropdown icon " + drop_down_class} onClick={this.open_setting_menu}>
<i className="setting icon"/>
<div className={"menu " + menu_class}>
<div className="item">Edit</div>
<div className="item">Delete</div>
</div>
</div>
</td>
</tr>
});
}
open_setting_menu() {
this.setState({
is_action_menu_active: !this.state.is_action_menu_active
});
}
render() {
return <table className="ui unstackable celled very basic striped table unstackable_very_basic_striped_table">
<thead>
<tr>
<th>{Language.name}</th>
<th>{Language.role}</th>
<th>{Language.department}</th>
<th>{Language.action}</th>
</tr>
</thead>
<tbody>
{this.getTableRows()}
</tbody>
</table>
}
}
export default UnstackableVeryBasicStripedUsersTable;
If you want to use a single component, you can save the index of the selected row in the state:
import React, { Component, PropTypes } from 'react';
import '../../../assets/custom_css/tables/unstackable_very_basic_striped_users_table.css';
import { v4 } from 'node-uuid';
import Language from '../../../assets/language';
class UnstackableVeryBasicStripedUsersTable extends Component {
static propTypes = {
rows: PropTypes.array.isRequired
};
constructor(props) {
super(props);
this.getTableRows = this.getTableRows.bind(this);
this.open_setting_menu = this.open_setting_menu.bind(this);
this.state = {
selected_row_index: 0,
is_action_menu_active: false
};
}
getTableRows() {
const { rows } = this.props;
return rows.map((row, index) => {
let drop_down_class = (this.state.is_action_menu_active && this.state.selected_row_index === index) ? "active" : "";
let menu_class = (this.state.is_action_menu_active && this.state.selected_row_index === index) ? "transition visible" : "";
return <tr key={v4()}>
{row.map(info => {
return <td key={v4()}>
{info}
</td>
})}
<td>
<div className={"ui right pointing dropdown icon " + drop_down_class} onClick={() => this.open_setting_menu(index)}>
<i className="setting icon"/>
<div className={"menu " + menu_class}>
<div className="item">Edit</div>
<div className="item">Delete</div>
</div>
</div>
</td>
</tr>
});
}
open_setting_menu(index) {
this.setState({
is_action_menu_active: !this.state.is_action_menu_active,
selected_row_index: index
});
}
render() {
return <table className="ui unstackable celled very basic striped table unstackable_very_basic_striped_table">
<thead>
<tr>
<th>{Language.name}</th>
<th>{Language.role}</th>
<th>{Language.department}</th>
<th>{Language.action}</th>
</tr>
</thead>
<tbody>
{this.getTableRows()}
</tbody>
</table>
}
}
export default UnstackableVeryBasicStripedUsersTable;

Resources