React web app- unique inputs from a table of variable lengths - javascript

I am building an app to help me learn react and solidity. I have a table which lists available products on the site. I want customers to be able to input the number of items they would like to buy for each product (each row) separately.
It works if I make all of them change a single variable in my state but stylistically I would like each row to have a unique value. To solve this I created an array to hold each value ( the array is called inputs). The tough part here is that the number of products (rows in the table) can vary so I cant just hard code it.
Rendering the table:
<tbody className="has-text-black-bis">
{indices.map((a) => (
<tr className="rows">
<td ><strong>{this.state.itemNames[a]}</strong></td>
<td ><strong>{this.state.costs[a]} Wei</strong></td>
<td ><strong>{this.state.quantities[a]}</strong></td>
//This row! <td >
Qty: <input type="number" className='table-input' name="inputs" value={this.state.inputs[a]} onChange={()=>this.handleTableInput(a)} />
<button type="button" className='buy-btn' onClick={()=>this.buyItem(a)}> Buy!</button>
</td>
</tr>
))}
</tbody>
Handling the input changes normally -This did not work when passing this.state.inputs[a] as the value as other rows would change as well:
handleInputChange = (event) => {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
I thought to change the above function to something like this where a (my index) is passed into the function. The problem is that I am not sure how to access the 'event' types like in the above generic function and thus not sure how to access the value that the user has input.
handleTableInput = (ind) => {
const {inputs} = this.state;
inputs[ind] = "value from table input";
this.setState({inputs: inputs})
}
Thank you for any help!

Related

Multiplying Row.values and user input values in Reactjs tables

I have a table that gets data(wages/hr & numberofPresentAttendances) from the backend database. I'm trying to create a UI inside the same table that allows the user to input values(Nos of overtime hours), which is then automatically used to compute totalWages = (wages/hr.numberofPresentAttendances.overtime).
filtered = this.state.data.map( (row, i) => {
if(row.department.toLowerCase().indexOf(this.state.departmentFilter) > -1){
return (
<tr key={"workerData"+i}>
<td>{i+1}</td>
<td>{row.name}</td>
<td>{row.numberofPresentAttendances}</td>
<td>{row.wages}</td>
<td>{row.totalWages}</td>
<td>{row.overtime}</td>
<td>overtime</td>
<td>
<input number="text" name="overtime" id="time" className="form-control"
placeholder="Enter Nos of overtime hrs..."
value={this.state.overtime}
onChange={this.handleOvertimeChange} required />
</td>
<td>{row.Tallowance}</td>
<td>
</td>
</tr>
I know this is not the right way to do this but I would like to do something like this :
constructor(props){
super(props);
this.state = {
totalWages:0,
};
this.onOvertimeChange = this.onOvertimeChange.bind(this);
}
onOvertimeChange(event){
this.setState({ totalWages: event.target.value*row.wages*row.numberofPresentAttendances });
}
If done this way, I will get several errors, one of the errors will be "Row not defined"
How do I get it to work so that it works on each row?
The function doesn't know what row is, pass it as an argument and create a closure on the onOvertimeChange function
...
onChange={this.handleOvertimeChange(row)} required />
...
and Update onOvertimeChange as below
const onOvertimeChange = (row) => (event) => {
this.setState({ totalWages: event.target.value*row.wages*row.numberofPresentAttendances });
}

How to dynamically set the default value in Reactive form?

I am creating the table with the looping, and if the loop value matches with the particular character I want to set the Reactive Form default value of Repeat else I want to set the empty value in the Reactive Form. Following Is my code
typescript
rDefault:string = "";
create(){
let form = this.fb.group({
rp:[this.rDefault]});
return form;
}
template
<tbody *ngFor="let c of ch.vl; let i = index" [formGroupName]="i">
<tr class="ui-widget-content ui-datatable">
<td>
{{rpm[ofs+i].label}}
</td>
<td>
<p-dropdown formControlName="rp" [options]="rpm[ofs+i].label =='REPEAT'? compOpt : []" appendTo="body" [disabled]='rpm[ofs+i].label == "REPEAT"?false:true'></p-dropdown>
</td>
</tr>
</tbody>
If {{rpm[ofs+i].label}} this value is equal to "Repeat" I want to set the default form values as "Repeat", else empty value. How can I achieve this?
Got the solution for my question but I don't know this is the correct way or not?
In the every loop I am calling the another method from [option] and setting the value in that method.
template file
<td>
<p-dropdown formControlName="rp" [options]="changeDefault(rpm[ofs+i].label)" appendTo="body" [disabled]='rpm[ofs+i].label == "REPEAT"?false:true'></p-dropdown>
</td>
Updated TS file
rDefault:string = "";
//this method will change value of rDefault
changeDefault(val){
this.rDefault = val == "REPEAT" ? "REPEAT" : "";
let rtnar = 'REPEAT'? this.compOpt : [];
return rtnar;
}
//
create(){
let form = this.fb.group({
rp:[this.rDefault]});
return form;
}

Dynamically Creating Table Rows With React

I am trying to create a Table using React and React-Bootstrap that has a custom number of table rows. The table is supposed to store data about player statistics of a certain video game, and based on the video game the statistics may change, thus the number of rows and titles of these rows must be able to dynamically change as well. I wanted to create an array in the state that held the list of current statistics, then map this array to a element using the map function and render the table. However, after trying several approaches I can't get any of the custom input to render. Below is the code :
Class Structure
class Statistics extends Component {
constructor(props) {
super(props)
this.state = {
game: '',
player_names: [],
positions: [],
stat_categories: [
'kills',
'deaths',
'assists'
]
}
}
renderTableRows(array) {
return (
<tr>
<th> NAME </th>
<th> TEAM </th>
<th> POSITION </th>
{ array.map(item => {
console.log(item)
<th key={item}> {item} </th>
})
}
</tr>
)
}
render() {
const columnLength = this.state.player_names.length
const statCols = this.state.stat_categories
return (
<div>
<MyNav url={this.props.location.pathname} />
<Table responsive striped bordered hover>
<thead>
{ this.renderTableRows(statCols) }
</thead>
</Table>
</div>
)
}
}
The console also properly logs the data in state (kills, deaths, assists) -- so the issue is when rendering the element. Any help would be appreciated!
You have no return statement in your map function, inside of renderTableRows.
When using ES6 arrow functions, you can either:
Return data directly without a return statement
(args) => (returnedData);
Or add some logic instead of just returning directly,
(args) => {
// Logic here
return returnedData
}
In the second case you'll need a return statement, because you are logging, if you choose to remove logging, go the first way.
Also, please post the code directly in your question, as using an image makes it less readable and not indexed by search engines.
You have to render each item in separate trs, not as a series of ths
renderTableCols(array) {
return array.map(item => <th>{item}</th>)
}
renderTableColValues(item, cols) {
return cols.map(col => <td>{item[col]}</td>)
}
renderTableRows(array) {
return array.map(item =>
<tr>
<td>{item.name}</td>
<td>{item.team}</td>
<td>{item.position}</td>
{this.renderTableColValues(item, this.cols)}
</tr>
);
}
render() {
return (
<Table>
<thead>
<tr>
<th>NAME</th>
<th>TEAM</th>
<th>POSITION</th>
{this.renderTableCols(this.cols)}
</tr>
</thead>
<tbody>
{this.renderTableRows(items)}
</tbody>
</Table>
);
}
More on tables https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table
I will give you a similar answer of what youre encoutering but its kinda different approach with a excelent solution
So, you are trying to create a dynamic table but youre making table rows static, what i did was letting the table to receive arrays of head and data and then create as many rows or datas that are required.
heres the code
export function objectIntoTableData(object) {
return Object.values(object).map((data, index) => {
return <td key={index}>{data}</td>;
});
}
You must change this index to (value,index) => , thats just my use
tableRows(data) {
return data.map(value => {
return <tr key={value.index}>{objectIntoTableData(value)}</tr>;
});
}
<thead>
<tr>
{head.map((value, index) => {
return <th key={index}>{value}</th>;
})}
</tr>
</thead>
<tbody>{this.tableRows(data)}</tbody>
Rather use a id or index inside your object since the index callback of the map function, its unsafe to use for the keys.
<ReactTableUse
head={["#", "Cell1", "Cell2", "Cell3"]}
data={[{id:1, test:1},{id:2, test:2}]}
/>
Rules:
When your state changes, render method of a class based component will be called.
Question: Who will change the state? will it grow inside the component ? What is your problem ? your are not being able to render anything ? or statistics is not dynamically rendering ? if you want to change it dynamically , you need to change the state first.

react.js how to get prop form tree element

I am trying to make table cell editable after clicking on icon in another cell , for that I need to get index of element so the editor will open in the correct row , which icon belongs to.
My issue is that I dont know the way i should get the prop value of table DOM element here is code for for clearify
a part of dom tree generated with react:
<tbody>
{stepsDone.map(function(step,idx) {
let content = step;
const editing = this.state.editing;
if(editing){
content = (
<form onSubmit={this._save}>
<input type="text" defaultValue={step} />
</form>
);
}
return(
<tr key={idx}>
<td className="step" data-step={'step'+idx}>{content}</td>
<td className="icRow">
<Icon className="edit" onClick={this._showEditor} rownum={idx}/>
<Icon className="remove"/>
<Icon className="trash outline"/>
</td>
</tr>
)
},this)}
show editor function:
_showEditor(e){
this.setState({
editing:{
row:e.target.rownum
}
});
console.log(this.state.editing);
}
After execution of showedtior function console logs :
first click = null , which is normal i think
more clicks = undefined , and thats whats brings a trouble i want to receive idx from map function.
here is code from Icon.js
import React from 'react';
import classNames from 'classnames';
export function Icon(props) {
const cssclasses = classNames('icon', props.className);
return <i className={cssclasses} onClick={props.onClick}/>;
}
if you want to reveive the idx from the map function you should pass it to the function _showEditor so your code must be like this :
<Icon className="edit" onClick={this._showEditor(idx)}/>
and the function definition should be :
_showEditor = (idx) => (event) => {
this.setState({
editing:{
row:idx
}
});
console.log(this.state.editing);
}
or if you don't want to use the arrow functions for some reason, just replace
onClick={this._showEditor(idx)}
with
onClick={this._showEditor.bind(this,idx)}
and its definition becomes
_showEditor(idx){...}

Prevent other fields in loop from changing with state

What's the best way to accomplish something like this. I have a credit note that I wish to edit the unit price. I'll pull in the invoice details then the user can edit/change the unit price. When I do this, all fields are changing. Somehow I need to put this in its own state? I have no idea how.
Parent Component using React.createClass:
line_items = [
{
id: 1,
unit_amount: "250.00",
...
},
{
...,
unit_amount: "20.00",
...
}
]
//render
<LineItems items={this.props.line_items} />
Child Component using React.createClass:
getInitialState(){
return{
creditedValues: []
}
},
_valueChange(e){
this.setState({creditedValues: this.state.creditedValues.concat([e.target.value])})
},
render(){
var items = this.props.items;
if (items.length !== 0) {
var lineItems = items.map(function(l){
return(
<tr key={l.id}>
<td data-label="Payment">{l.description}</td>
<td className="credit-note-qty" data-label="Issue Date">{l.quantity}</td>
<td className="credit-note-up" data-label="Amount">{l.unit_amount}</td>
<td className="credit-note-amt" data-label="Period">
<input type="number" value={this.state.newValue} onChange={this._valueChange} />
</td>
</tr>
)
}.bind(this));
};
return(<tbody>{lineItems}</tbody>)
}
Before I changed to concat, when I change a value, all values change at the same time. How to prevent this?
Edit:
I have updated the _valueChange with:
_valueChange(i, e){
e.preventDefault();
var obj = this.props.creditNote.line_items;
var num = obj.find(p => i === p.id);
num.unit_amount = e.target.value;
this.setState({creditedValues : num});
}
Input now changes to:
<input type="number" value={this.state.newValue} onChange={this._valueChange.bind(this, l.id)} />
Now I have exactly what I want but there is a but. If the user update another input field, it replace the entire state. True, as the code I have replaces the entire state. If the user update two or more fields, how to keep the new objects?

Categories

Resources