I have created a component that sorts and filters an HTML table. The functionality is correct but I have a problem where my table renders "No asset records found." but when I click on one of the headers it displays the contents of the data array in state. I am truly stuck and confused on this strange behaviour. I think the problem might be with the filterAssets function because if I change from this:
let filterAssets = this.state.data.filter(a => {
return a.name.toLowerCase().indexOf(this.state.search) !== -1
})
to this:
let filterAssets = this.props.assetManagement.filter(a => {
return a.name.toLowerCase().indexOf(this.state.search) !== -1
})
Here is the code below if it helps
import React, { Component, Fragment } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { getAssetManagement } from '../../actions/asset-management'
class AssetManagement extends Component {
static propTypes = {
assetManagement: PropTypes.array.isRequired,
getAssetManagement: PropTypes.func.isRequired
}
componentDidMount() {
this.props.getAssetManagement()
}
state = {
name: '',
search: '',
data: []
}
sortBy = this.sortBy.bind(this)
compareBy = this.compareBy.bind(this)
onSubmit = e => {
e.preventDefault()
}
onChange = e =>
this.setState({
[e.target.name]: e.target.value
})
updateSearch = e =>
this.setState({
search: e.target.value.substr(0, 20)
})
compareBy(key) {
return (a, b) => {
if (a[key] < b[key]) return -1
if (a[key] > b[key]) return 1
return 0
}
}
sortBy(key) {
let arrayCopy = [...this.props.assetManagement]
this.state.data.sort(this.compareBy(key))
this.setState({ data: arrayCopy })
}
render() {
let filterAssets = this.state.data.filter(a => {
return a.name.toLowerCase().indexOf(this.state.search) !== -1
})
return (
<Fragment>
{/* Search input */}
<div class="input-group mb-1">
<div class="input-group-prepend">
<span class="input-group-text btn-secondary">
<i class="fas fa-search" />
</span>
</div>
<input
className="form-control"
type="text"
placeholder="Search Asset"
onChange={this.updateSearch.bind(this)}
value={this.state.search}
/>
</div>
{/* Asset management table */}
<div className="table-responsive">
<table className="table table-bordered text-center">
<thead>
<tr>
<th onClick={() => this.sortBy('id')}>ID</th>
<th onClick={() => this.sortBy('name')}>Name</th>
</tr>
</thead>
<tbody>
{filterAssets != 0 ? (
filterAssets.map(a => (
<tr key={a.id}>
<td>{a.id}</td>
<td>{a.name}</td>
</tr>
))
) : (
<tr>
<td colSpan={6}>No asset records found.</td>
</tr>
)}
</tbody>
</table>
</div>
</Fragment>
)
}
}
const mapStateToProps = state => ({
assetManagement: state.assetManagement.assetManagement
})
export default connect(
mapStateToProps,
{ getAssetManagement }
)(AssetManagement)
Change filterAssets != 0 to filterAssets.length > 0
One first render:
let filterAssets = this.state.data.filter(a => {
return a.name.toLowerCase().indexOf(this.state.search) !== -1
})
Your this.state.data is empty, only this.props.assetManagement available if you handle redux properly so no wonder it you cannot get anything from filtering.
Btw: filterAssets != 0 is absolutely wrong, so go ahead and change this line first.
When you use the alternative syntax for a React Component without using a constructor you no longer have access to props. So if you go back to using a standard constructor the problem disappears, e.g.:
constructor(props) {
super(props);
this.state = {
name: "",
search: "",
data: this.props.assetManagement
};
this.sortBy = this.sortBy.bind(this);
this.compareBy = this.compareBy.bind(this);
}
The real problem you have here is that you have two source of data: state.data and props.assetManagement - you retrieve from redux and get newest data from props.assetManagement, but when you need to trigger sorting, you make a copy to state.data. Then problem arises since you don't copy from props.assetManagement to state.data until you trigger sortBy function.
A solution for that is to get rid of state.data and store the sorting key in state. You can update, reset that key value, and sorting logic should be apply to props.assetManagement only:
class AssetManagement extends Component {
static propTypes = {
assetManagement: PropTypes.array.isRequired,
getAssetManagement: PropTypes.func.isRequired
}
componentDidMount() {
this.props.getAssetManagement()
}
state = {
name: '',
search: '',
sortingKey: ''
}
sortBy = this.sortBy.bind(this)
compareBy = this.compareBy.bind(this)
onSubmit = e => {
e.preventDefault()
}
onChange = e =>
this.setState({
[e.target.name]: e.target.value
})
updateSearch = e =>
this.setState({
search: e.target.value.substr(0, 20)
})
compareBy(key) {
return (a, b) => {
if (a[key] < b[key]) return -1
if (a[key] > b[key]) return 1
return 0
}
}
sortBy(key) {
if (key !== this.state.sortingKey) {
this.setState({ sortingKey: key });
}
}
render() {
let sortAssets = !!this.state.sortingKey ?
this.props.assetManagement.sort(this.compareBy(this.state.sortingKey)) :
this.props.assetManagement;
let filterAssets = sortAssets.filter(a => {
return a.name.toLowerCase().indexOf(this.state.search) !== -1
});
return (
<Fragment>
{/* Search input */}
<div class="input-group mb-1">
<div class="input-group-prepend">
<span class="input-group-text btn-secondary">
<i class="fas fa-search" />
</span>
</div>
<input
className="form-control"
type="text"
placeholder="Search Asset"
onChange={this.updateSearch.bind(this)}
value={this.state.search}
/>
</div>
{/* Asset management table */}
<div className="table-responsive">
<table className="table table-bordered text-center">
<thead>
<tr>
<th onClick={() => this.sortBy('id')}>ID</th>
<th onClick={() => this.sortBy('name')}>Name</th>
</tr>
</thead>
<tbody>
{filterAssets != 0 ? (
filterAssets.map(a => (
<tr key={a.id}>
<td>{a.id}</td>
<td>{a.name}</td>
</tr>
))
) : (
<tr>
<td colSpan={6}>No asset records found.</td>
</tr>
)}
</tbody>
</table>
</div>
</Fragment>
)
}
}
Sample code: https://codesandbox.io/s/n91pq7073l
Related
The code that I posted below is the API request from which I make a table. This table has 4 columns: id, userid, title. I want to understand how I can sort by userid and title, as shown in the photo. It would be great if the steps were described in detail.
I'm trying to group the tab as shown in the photo, but I can't.
Can you suggest/show me how to do this?
Also wanted to know how to reset the group value of a column?
I will be grateful for any help.
My code:
import React from "react";
import "./GroupByUserID.css";
import { Link } from "react-router-dom";
export default class GroupByUserID extends React.Component {
// Constructor
constructor(props) {
super(props);
this.state = {
items: [],
};
}
componentDidMount = () => {
this.apiFetch();
};
//Fetch data from API
apiFetch = () => {
return fetch("https://jsonplaceholder.typicode.com/todos")
.then((res) => res.json())
.then((json) => {
this.setState((prevState) => {
return { ...prevState, items: json };
});
});
};
// Sort UserID
setSortedItemsUserID = () => {
const { items } = this.state;
const sortedUserID = items.sort((a, b) => {
if (a.userId < b.userId) {
return items.direction === "ascending" ? -1 : 1;
}
if (a.userId > b.userId) {
return items.direction === "ascending" ? 1 : -1;
}
return 0;
});
console.log(sortedUserID);
this.setState((prevState) => {
return { ...prevState, items: sortedUserID };
});
};
render() {
const { items } = this.state;
return (
<div>
<h1>Home Page</h1>
<table>
<thead>
<tr>
<th>
<Link target="self" to="/">
View Normal
</Link>
</th>
<th>Group By UserID</th>
</tr>
</thead>
<thead>
<tr>
<th>
User ID
<button
type="button"
onClick={() => this.setSortedItemsUserID()}
>
⬇️
</button>
</th>
<th>Title</th>
</tr>
</thead>
<tbody>
{items.map((item) => (
<tr key={item.userId + item.title}>
<td>{item.userId}</td>
<td>{item.title}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
}
i am trying to sort html table by ASC and desc order but this table is not working properly its working only for first column when i put id name can you please help me for make this work sorting func by ASC and Desc. this is my code so far i tried but its not working Thanks
import React from "react";
class Th extends React.Component {
handleClick = () => {
const { onClick, id } = this.props;
onClick(id);
};
render() {
const { value } = this.props;
return <th onClick={this.handleClick}>{value}</th>;
}
}
class App extends React.Component {
state = {
users: []
};
async componentDidMount() {
const res = await fetch(
`https://run.mocky.io/v3/6982a190-6166-402e-905f-139aef40e6ef`
);
const users = await res.json();
this.setState({
users
});
}
handleSort = id => {
this.setState(prev => {
return {
[id]: !prev[id],
users: prev.users.sort((a, b) =>
prev[id] ? a[id] < b[id] : a[id] > b[id]
)
};
});
};
render() {
const { users } = this.state;
return (
<table>
<thead>
<tr>
<Th onClick={this.handleSort} id="mileage" value="Mileage" />
<Th onClick={this.handleSort} id="overall_score" value="Overall score" />
<Th onClick={this.handleSort} id="fuel_consumed" value="Fuel Consumed" />
</tr>
</thead>
<tbody>
{users.map(user => (
<tr>
<td>{user.span.mileage.value}</td>
<td>{user.span.overall_score.value}</td>
<td>{user.span.fuel_consumed.value}</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default App;
To make it works you need to change a few thigs:
the setState merges new data with old one, so [id]: !prev[id] adds new property to state for each column you filter without removing old one. It's better to store column to filter in dedicated state property (e.g. sortBy).
fix sorting function to make it sorting the users by correct object properties
remove async from componentDidMount and change fetch to use then/catch instead of async/await (it makes your code more React-ish).
Use example below as an inspiration:
class App extends React.Component {
state = {
sortBy: null,
order: "ASC",
users: []
};
componentDidMount() {
fetch(`https://run.mocky.io/v3/6982a190-6166-402e-905f-139aef40e6ef`)
.then(response => response.json())
.then(users => this.setState({users}))
.catch(err => console.log('Error', err));
}
handleSort = id => {
this.setState(prev => {
const ordered = prev.users.sort((a, b) =>
prev.order === "ASC"
? a["span"][id]["value"] < b["span"][id]["value"]
: a["span"][id]["value"] > b["span"][id]["value"]
);
return {
sortBy: id,
order: prev.order === "ASC" ? "DESC" : "ASC",
users: ordered
};
});
};
render() {
const { users } = this.state;
return (
<table>
<thead>
<tr>
<Th onClick={this.handleSort} id="mileage" value="Mileage" />
<Th
onClick={this.handleSort}
id="overall_score"
value="Overall score"
/>
<Th
onClick={this.handleSort}
id="fuel_consumed"
value="Fuel Consumed"
/>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr>
<td>{user.span.mileage.value}</td>
<td>{user.span.overall_score.value}</td>
<td>{user.span.fuel_consumed.value}</td>
</tr>
))}
</tbody>
</table>
);
}
}
class Th extends React.Component {
handleClick = () => {
const { onClick, id } = this.props;
onClick(id);
};
render() {
const { value } = this.props;
return <th onClick={this.handleClick}>{value}</th>;
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Keep in mind that in only works with the current data schema and fields you already have. If you want to change the fields to sort by you need to update sorting function.
I am trying to explore searching and narrowing down to certain results. Problem is that search based on output from one filed is working ok but when i want to narrow down the results using second or third filed it is not working. I think issue is in here i do not know how to modify it to have this narrow results.
if (this.state.data !== null) {
result = this.state.data.filter(state => {
const regex = new RegExp(
`^${this.state.name || this.state.email || this.state.body}`,
"gi"
);
return (
state.name.match(regex) ||
state.email.match(regex) ||
state.body.match(regex)
);
});
}
Search
import React, { Component } from "react";
import Table from "./Table";
import axios from "axios";
export default class Main extends Component {
state = {
data: null
};
onChange = e => {
this.setState({
[e.target.name]: e.target.value
});
axios
.get("https://jsonplaceholder.typicode.com/comments")
.then(res =>
this.setState({
data: res.data
})
)
.catch(err => console.log(err));
};
render() {
let result;
if (this.state.data !== null) {
result = this.state.data.filter(state => {
const regex = new RegExp(
`^${this.state.name || this.state.email || this.state.body}`,
"gi"
);
return (
state.name.match(regex) ||
state.email.match(regex) ||
state.body.match(regex)
);
});
}
console.log(this.state.name);
console.log(this.state.email);
console.log(this.state.body);
console.log(result);
console.log(this.state.data);
return (
<div>
<table>
<thead>
<tr>
<th>
<input
label="Name"
name="name"
placeholder="Name "
onChange={this.onChange}
/>
</th>
<th>
<input
label="Name"
name="email"
placeholder="Email "
onChange={this.onChange}
/>
</th>
<th>
<input
label="Name"
name="body"
placeholder="Body "
onChange={this.onChange}
/>
</th>
</tr>
</thead>
{result !== undefined ? <Table data={result} /> : <p>Loading</p>}
</table>
</div>
);
}
}
Table.js
import React, { Component } from "react";
export default class Table extends Component {
render() {
const { data } = this.props;
console.log(data);
return (
<tbody>
{data.map(el => (
<tr key={el.id}>
<td>{el.name}</td>
<td>{el.email}</td>
<td>{el.body}</td>
</tr>
))}
</tbody>
);
}
}
I think if you are okay with using .startsWith(), this would be a very clean and readable solution without Regular Expressions. They might not be necessary here:
result = this.state.data.filter(record => {
return (
record.name.starsWith(this.state.name) ||
record.email.starsWith(this.state.email) ||
record.body.starsWith(this.state.body)
);
});
.startsWith() is not supported by IE, but you can polyfill it as described here:
if (!String.prototype.startsWith) {
Object.defineProperty(String.prototype, 'startsWith', {
value: function(search, pos) {
pos = !pos || pos < 0 ? 0 : +pos;
return this.substring(pos, pos + search.length) === search;
}
});
}
EDIT:
If you want all the filters to match, just use && instead of ||. Also, if you want to find the string anywhere in the data (and not just at the beginning) the code could look like this:
result = this.state.data.filter(record => {
return (
record.name.indexOf(this.state.name) !== -1 &&
record.email.indexOf(this.state.email) !== -1 &&
record.body.indexOf(this.state.body) !== -1
);
});
This still avoids RegEx, because it's not really necessary here.
I realise this question has been asked quite a few times:
React.createElement: type is invalid -- expected a string
Element type is invalid: expected a string (for built-in components) or a class/function
Warning: React.createElement: type is invalid -- expected a string (for built-in components)
...but none of these have helped me. I get from these questions/answers that the general consensus is that this commonly happens with an import/export issue which (looking through my code base, is not the issue I have).
I have 3 components: CognitoLogin, ListGroups and InviteUserModal. CogntioLogin is an auth service and redirects to ListGroups using react-router-dom <Redirect>. This then renders InviteUserModal on the click of a button. This is my code (the bits that matter):
CognitoLogin.js
render() {
const { redirect } = this.state;
if (redirect) {
return <Redirect to={{
pathname: redirect,
state: {
email: this.state.email
}
}}/>;
}
return (
<div id="dialog-region">
<div className="login-view">
{ !this.props.location.state.loginAuth &&
<CognitoMessage email={this.state.email}/>
}
<div id="login-logo">
<Image id="pimberly-logo" src={PimberlyLogo} responsive/>
<Image id="cognito-logo" src={CognitoLogo} responsive/>
</div>
<LoginForm handleLogin={this.handleLogin}/>
<div className="checkbox forgottenReset">
<a id="reset-password" onClick={this.handlePasswordReset}>Forgotten password?</a>
</div>
<div className="checkbox forgottenReset">
<a id="resend-confirm" onClick={this.handleResend} href="#">Resend confirmation</a>
</div>
{ this.state.err &&
<ErrorAlert err={this.state.err} />
}
{ this.state.apiRes &&
<SuccessAlert res={this.state.apiRes} />
}
{ this.state.resend &&
<InfoAlert email={this.state.email}/>
}
</div>
</div>
);
}
ListGroups.js
import React, { Component } from "react";
import axios from "axios";
import { connect } from "react-redux";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { Table, ButtonToolbar, Button } from "react-bootstrap";
// Selectors
import { getUser } from "../selectors/user";
import { getGroupByName } from "../selectors/group";
// Actions
import { setUsersGroupsPersist } from "../actions/user";
// Components
import ErrorAlert from "./ErrorAlert";
import ListUsersModal from "./ListUsersModal";
import InviteUsersModal from "./InviteUsersModal";
// Styles
import "../../css/login.css";
class ListGroups extends Component {
constructor(props) {
super(props);
this.state = {
groups: [],
showModal: false,
groupName: "",
emailOfUser: props.location.state.email
};
this.handleRemoveInviteUsers = this.handleRemoveInviteUsers.bind(this);
this.closeModal = this.closeModal.bind(this);
this.props.setUsersGroupsPersist(this.state.emailOfUser);
}
handleRemoveInviteUsers(event) {
const groupSelected = this.props.groups.find((group) => {
return (group.name === event.currentTarget.dataset.groupName);
});
event.preventDefault();
this.setState({
groupAction: event.currentTarget.dataset.groupAction
});
return axios.post(`http://${process.env.BASE_API}/groups/users/list`, {
groupName: groupSelected.name
})
.then((res) => {
this.setState({
showModal: true,
users: res.data.Users
});
})
.catch((err) => {
this.setState({
apiErr: err
});
});
}
closeModal(event) {
this.setState({
showModal: false
});
}
render() {
const Modal = (this.state.groupAction === "remove") ? <ListUsersModal /> : <InviteUsersModal />;
return (
<>
<div id="dialog-region">
<Table striped bordered condensed hover>
<thead>
<tr>
<th scope="col" className="col-name">Name</th>
<th scope="col" className="col-description">Description</th>
<th scope="col" className="col-created">Created</th>
<th scope="col" className="col-buttons"></th>
</tr>
</thead>
<tbody>
{ this.props.groups.map(function(group, i) {
return <tr key={i}>
<td>{ group.name }</td>
<td>{ group.description }</td>
<td>{ group.created }</td>
<td>
<ButtonToolbar>
<Button bsStyle="primary" onClick={this.handleRemoveInviteUsers} data-group-name={group.name} data-group-action="invite">
<FontAwesomeIcon icon="plus"/> Invite users
</Button>
<Button bsStyle="danger" onClick={this.handleRemoveInviteUsers} data-group-name={group.name} data-group-action="remove">
<FontAwesomeIcon icon="trash"/> Remove users
</Button>
</ButtonToolbar>
</td>
</tr>;
}, this)}
</tbody>
</Table>
{ this.state.showModal &&
<Modal closeModal={this.closeModal}/>
}
{ this.state.apiErr &&
<ErrorAlert err={this.state.apiErr} />
}
</div>
</>
);
}
}
const mapStateToProps = (state, props) => {
const user = getUser(state, props.location.state.email);
const groups = user.groups.map((groupName) => {
return getGroupByName(state, groupName);
});
return {
user,
groups
};
};
export default connect(
mapStateToProps,
{ setUsersGroupsPersist }
)(ListGroups);
InviteUsersModal.js
import React, { Component } from "react";
import axios from "axios";
import { connect } from "react-redux";
import { Modal, Button, FormGroup, FormControl, ControlLabel, Table } from "react-bootstrap";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import ErrorAlert from "./ErrorAlert";
export default class InviteUsersModal extends Component {
constructor(props) {
super(props);
this.state = {
currentEmail: "",
emailsToInvite: []
};
this.handleInviteUser = this.handleInviteUser.bind(this);
this.handleEmailChange = this.handleEmailChange.bind(this);
}
handleInviteUsers(event) {
event.preventDefault();
return axios.post(`http://${process.env.BASE_API}/groups/users/invite`, {
emails: this.state.emailsToInvite,
emailOfInviter: this.props.emailOfUser,
groupName: this.props.groupName
})
.then((res) => {
this.props.closeModal();
})
.catch((err)=> {
this.setState({
apiErr: err
});
});
}
handleSubmit(event) {
this.setState({
emailsToInvite: [...this.state.emailsToInvite, this.state.currentEmail]
});
}
handleRemoveInvite(event) {
const emailToRemove = event.currentTarget.dataset.email;
event.preventDefault();
this.state.emailsToInvite.splice(this.state.emailsToInvite.indexOf(emailToRemove), 1);
}
handleEmailChange(event) {
this.setState({
currentEmail: event.currentTarget.value
});
}
handleModalClose() {
this.props.closeModal();
}
render() {
return (
<Modal show={this.props.showModal} onHide={this.handleModalClose} bsSize="large">
<Modal.Header closeButton>
<Modal.Title>Invite</Modal.Title>
</Modal.Header>
<Modal.Body>
<form role="form" onSubmit={this.handleSubmit}>
<FormGroup controlId="inviteUsersForm">
<FormControl type="email" value={this.state.currentEmail} placeholder="Email" onChange={this.handleEmailChange}></FormControl>
<Button type="submit" bsStyle="primary">
<FontAwesomeIcon icon="plus"/> Invite
</Button>
</FormGroup>
</form>
<hr/>
<h1>Users to invite</h1>
<Table striped bordered condensed hover>
<thead>
<tr>
<th scope="col">Email</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{ this.state.emailsToInvite.map(function(email, i) {
return <tr key={i}>
<td>{ email }</td>
<td>
<Button bsStyle="danger" onClick={this.handleRemoveInvite} data-email={email}>
<FontAwesomeIcon icon="minus"/>
</Button>
</td>
</tr>;
}, this)}
</tbody>
</Table>
{ this.state.apiErr &&
<ErrorAlert err={this.state.apiErr}/>
}
</Modal.Body>
<Modal.Footer>
<Button onClick={this.handleModalClose}>Cancel</Button>
<Button onClick={this.handleInviteUsers} bsStype="primary">Save</Button>
</Modal.Footer>
</Modal>
);
}
}
As it looks, I am importing the component correctly. When debugging, the constructor is not even hit so am unsure where it is going wrong.
This did work before I introduced react-router/redux so is is possible they are the problem?
The error(s) happen when I click on the button to "Invite users". When debugging, I can get to the line to show the modal and any further than that will throw an error.
The full list of errors are:
EDIT
I have also tried rendering the InviteUsersModal via element variables instead so the render method would be:
render() {
const Modal = (this.state.groupAction === "remove") ? : ;
return (
<>
<div id="dialog-region">
<Table striped bordered condensed hover>
...
</Table>
{ this.state.showModal &&
{Modal}
}
...
</div>
</>
);
}
but get similar errors:
I think you should adjust the following line:
const Modal = (this.state.groupAction === "remove") ? <ListUsersModal /> : <InviteUsersModal />;
To:
const Modal = (this.state.groupAction === "remove") ? ListUsersModal : InviteUsersModal;
You are assigning an instance of the component to variable Modal and instead of rendering it you are using it as Component
I have a table where there is one input box in each row. There are 3 rows in total and i need to calculate the total from those three input boxes value. But the state of value is not updating. I only get the initial state of value. For example, there is a state object of agent, hotel, admin. If i initialize the agent value 10, i get 10 in input box but when i try to change the value i only get 10. The value does not gets updated.
Here is the code
const Tbody = ({ roles, states, onChange, onBlur }) => {
const row = roles.map((role, index) => (
<tr key={index}>
<td>{index + 1}</td>
<td>{role.label}</td>
<td>
<TextFieldGroup
id="formControlsText"
type="number"
name={role.name}
value={states[role.name]}
onChange={event => onChange(event)}
onBlur={event => onBlur(event)}
error={states.errors[role.name]}
required
/>
</td>
</tr>
));
return <tbody>{row}</tbody>;
};
class Commission extends React.PureComponent {
state = {
agentCommission: 0,
hotelCommission: 0,
adminCommission: 0,
errors: {},
isSubmitted: false
};
handleChange = event => {
console.log(event.target);
const fieldName = event.target.name;
this.setState(
{ [event.target.name]: parseFloat(event.target.value) },
() => {
this.validateField([fieldName]);
}
);
};
handleBlur = event => {
const fieldName = event.target.name;
this.validateField([fieldName]);
};
validateField = validate => {
const errors = { ...this.state.errors };
let hasError = false;
validate.forEach(field => {
if (
parseFloat(this.state[field]) > 100 ||
parseFloat(this.state[field]) < 0
) {
hasError = true;
errors[field] = 'cannot be less than 0 and more than 100';
} else {
errors[field] = '';
}
});
this.setState({ errors });
return !hasError;
};
render() {
const { agentCommission, adminCommission, hotelcommission } = this.state;
const totalCommission = agentCommission + adminCommission + hotelcommission;
console.log('totalCommission', totalCommission);
return (
<div className="table-responsive">
<table className="table table-striped table-bordered table-condensed">
<thead>
<tr>
<th>S.N</th>
<th>Role</th>
<th>Commission</th>
</tr>
</thead>
<Tbody
roles={[
{ name: 'agentCommission', label: 'Agent' },
{ name: 'hotelCommission', label: 'Hotel Owner' },
{ name: 'adminCommission', label: 'Admin' }
]}
states={{ ...this.state }}
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
<tbody>
<tr>
<td>
<button
className="btn btn-primary"
onClick={this.handleSubmit}
disabled={totalCommission === 100 ? false : true}>
Save Changes
</button>
</td>
</tr>
</tbody>
</table>
</div>
);
}
}
In ReactJS, when you extend a React component class, you must initialize the state in the constructor. Also, you need to call the parent class' constructor via super(props). This is the only way that the React library's class can get access to your state values, as well as provide access in methods such as setState()
https://codepen.io/julianfresco/pen/ybrZNe/
class Commission extends React.PureComponent {
constructor(props, context) {
super(props)
this.state = {
agentCommission: 0,
hotelCommission: 0,
adminCommission: 0,
errors: {},
isSubmitted: false
};
// method instance bindings
this.handleChange = this.handleChange.bind(this)
this.handleBlur = this.handleBlur.bind(this)
this.validateField = this.validateField.bind(this)
}
// ...
// you had 1 typo in the render function, hotelCommission wasn't camel case
render() {
const { agentCommission, adminCommission, hotelCommission } = this.state;
// ...
}
}
The issue is the Commission class, where you are not initializing the state.
Your code should be like the following:
const Tbody = ({ roles, states, onChange, onBlur }) => {
const row = roles.map((role, index) => (
<tr key={index}>
<td>{index + 1}</td>
<td>{role.label}</td>
<td>
<input
id="formControlsText"
type="number"
name={role.name}
value={states[role.name]}
onChange={event => onChange(event)}
onBlur={event => onBlur(event)}
error={states.errors[role.name]}
required
/>
</td>
</tr>
));
return <tbody>{row}</tbody>;
};
class Commission extends React.PureComponent {
constructor(props) {
super(props)
this.state = {
agentCommission: 0,
hotelCommission: 0,
adminCommission: 0,
errors: {},
isSubmitted: false
};
}
handleChange(event) {
console.log(event.target);
const fieldName = event.target.name;
this.setState(
{ [event.target.name]: parseFloat(event.target.value) },
() => {
this.validateField([fieldName]);
}
);
};
handleBlur(event) {
const fieldName = event.target.name;
this.validateField([fieldName]);
};
validateField(validate) {
const errors = { ...this.state.errors };
let hasError = false;
validate.forEach(field => {
if (
parseFloat(this.state[field]) > 100 ||
parseFloat(this.state[field]) < 0
) {
hasError = true;
errors[field] = 'cannot be less than 0 and more than 100';
} else {
errors[field] = '';
}
});
this.setState({ errors });
return !hasError;
};
render() {
const { agentCommission, adminCommission, hotelcommission } = this.state;
const totalCommission = agentCommission + adminCommission + hotelcommission;
console.log('totalCommission', totalCommission);
return (
<div className="table-responsive">
<table className="table table-striped table-bordered table-condensed">
<thead>
<tr>
<th>S.N</th>
<th>Role</th>
<th>Commission</th>
</tr>
</thead>
<Tbody
roles={[
{ name: 'agentCommission', label: 'Agent' },
{ name: 'hotelCommission', label: 'Hotel Owner' },
{ name: 'adminCommission', label: 'Admin' }
]}
states={{ ...this.state }}
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
<tbody>
<tr>
<td>
<button
className="btn btn-primary"
onClick={this.handleSubmit}
disabled={totalCommission === 100 ? false : true}>
Save Changes
</button>
</td>
</tr>
</tbody>
</table>
</div>
);
}
}
Fiddle demo: https://codesandbox.io/s/KO3vDRGjR