Append data as table in react js - reactjs

I have an application what takes the date from inputs and saving it, the data are apendded bellow, something like todo list. But now i have an issue trying to display the data, because i want to display it in a specific order as table row, but now the data is not showing properly, because i want to have Name under Name, Old under old, delete button under delete Text, ad edit button under edit text.
How to do this?
link to my application: https://codesandbox.io/s/nifty-moore-g86kd

There are a few issues in your code.
You don't have any state to keep track of the added users
On a form submit instead of updating the data you're trying to directly update the DOM with submitted data. Which is not the right way to do things in react.
import React, { useState, useEffect } from "react";
export default function App() {
const [user, setUser] = useState({
name: "",
old: ""
});
// A new state to keep track of the added users
const [users, setUsers] = useState([]);
const changeUser = e => {
const v = e.target.value;
setUser({
...user,
[e.target.name]: v
});
};
// On form submit, all you need to do is to update the users state
// Then render will take care of the rest
const submitForm = e => {
e.preventDefault();
setUsers([...users, user]);
};
// This is how in react we update the content
// Whenever, there is a change in state, this will get called and content will be updated
// Ps: It's being called in the render
const renderBody = () => {
const content = [];
users.map(item => {
content.push(
<tr>
<td>{item.name}</td>
<td>{item.old}</td>
<td>Delete btn</td>
<td>Edit btn</td>
</tr>
);
});
return content;
};
return (
<div className="to-do">
<form action="" onSubmit={submitForm}>
<label htmlFor="">
Name
<input
name="name"
onChange={changeUser}
value={user.name}
type="text"
/>
</label>
<label htmlFor="yes">
Old Yes
<input
id="yes"
name="old"
onChange={changeUser}
value="yes"
type="radio"
/>
</label>
<label htmlFor="no">
Old No
<input
id="no"
name="old"
onChange={changeUser}
value="no"
type="radio"
/>
</label>
<input value={user.old} type="submit" value="SUBMIT" />
</form>
<div className="res">
<table>
<tr>
<th>Name</th>
<th>OLD</th>
<th>DELETE</th>
<th>Edit</th>
</tr>
<tr id="res" />
{renderBody()}
</table>
</div>
</div>
);
}
<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>
So what you need
State for users to keep track of the added users
On form submit, a trigger to update that users state
A loop, to iterate over users array and return table rows with content

Related

Weird return of data after splice and setstate

Here's simulate in codesandbox
https://codesandbox.io/embed/epic-nash-mxteu?fontsize=14&hidenavigation=1&theme=dark
I am having a weird behavior when I remove a row from the dynamic rows created.
I have removed the row with 2 b. As you can see the console log has the correct data while the UI showing wrong data. It means the function works well but somehow displaying incorrectly.
Anyone have idea why? Thanks in advance
Screenshots
Before remove row
After remove row
Source code
const [gavRows, setGAVRows] = useState([]);
const handleGAVAddRow = async () => {
try {
const item = {
gav_field: '',
gav_value: ''
};
setGAVRows([...gavRows, item]);
} catch (error) {
console.log('error', error)
}
};
const handleGAVRemoveSpecificRow = (idx) => {
console.log('idx', idx)
const tempRows = [...gavRows];
console.log('tempRows', tempRows)
tempRows.splice(idx, 1);
setGAVRows(tempRows)
};
const handleGAVChange = async (idx, e) => {
const { name, value } = e.target;
var tempRows = [...gavRows];
tempRows[idx][name] = value;
setGAVRows(tempRows)
};
<table className="customgav_section">
<tbody>
{
gavRows.map((item, idx) => {
console.log('map idx', idx, item)
return (
<tr key={idx}>
<td>
<Input type="text"
name="gav_field" id="gav_field"
value={gavRows[idx].field}
onChange={(e) => handleGAVChange(idx, e)}
/>
</td>
<td>
<Input type="text"
name="gav_value" id="gav_value"
value={gavRows[idx].value}
onChange={(e) => handleGAVChange(idx, e)}
/>
</td>
<td>
<Button outline color="danger" onClick={() => handleGAVRemoveSpecificRow(idx)}><FaMinus /></Button>
</td>
</tr>)
})
}
</tbody>
</table>
Your problem is that you are using the index of the array as the key.
Read why that is bad: https://robinpokorny.medium.com/index-as-a-key-is-an-anti-pattern-e0349aece318
A quick hack was assigning a random number as the key of each item in the gavRows and using that as the key of the element. See updated code: https://codesandbox.io/s/charming-bouman-03zn7
Also, the id of an element must be unique in the DOM so i removed those from the input elements.
Codesandbox
The problem is you put the wrong input value.
Remember the item object you set is:
const item = {
gav_field: "", //not field
gav_value: "" //not value
};
You should modify code from
<td>
<Input
type="text"
name="gav_field"
id="gav_field"
value={gavRows[idx].field}
onChange={(e) => handleGAVChange(idx, e)}
/>
</td>
<td>
<Input
type="text"
name="gav_value"
id="gav_value"
value={gavRows[idx].value}
onChange={(e) => handleGAVChange(idx, e)}
/>
</td>
To:
<td>
<Input
type="text"
name="gav_field"
id="gav_field"
value={gavRows[idx].gav_field}
onChange={(e) => handleGAVChange(idx, e)}
/>
</td>
<td>
<Input
type="text"
name="gav_value"
id="gav_value"
value={gavRows[idx].gav_value}
onChange={(e) => handleGAVChange(idx, e)}
/>
</td>

How to draw a dynamic table with inputs for the user in some rows?

I want to draw a table with a list of items, and have inputs for each of the items. I managed to draw the table and render the items from a get request. The goal is to take inputs for each item in different rows. However, since the items are rendered using a map function, when the user types something in an input, it gets rendered in all the rows. I need it to get rendered only on its respective row. I understand why it is happening but I dont know how to do it. Im new to React so some help would be appreciated.
Code for rendering the table
<div className="d-flex justify-content-center card card-body border border-5 border-primary">
<div className="table table-striped text-primary table-container">
<thead className="border border-light">
<tr>
<th>Nombre</th>
<th>Sabor</th>
<th>Precio</th>
<th>Cantidad</th>
<th>Empaque</th>
<th>Instrucciones Adicionales</th>
</tr>
</thead>
<tbody>
{sweets.map(sweet => (
<tr key={sweet.sweet_id}>
<td>{sweet.s_name}</td>
<td>{sweet.s_flavor}</td>
<td>{sweet.s_price}</td>
<td>
<input
type="text"
onChange={e => set_sweet_quantity(e.target.value)}
value={sweet_quantity}
className="form-control"
/>
</td>
<td>
<input
type="number"
onChange={e => set_sweet_package(e.target.value)}
value={sweet_package}
className="form-control"
/>
</td>
<td>
<input
type="text"
onChange={e => set_additional_instructions(e.target.value)}
value={additional_instructions}
className="form-control"
placeholder='N/A'
/>
</td>
<td>
<button className="btn btn-primary btn-sm btn-block"
onClick={() => handleItemSweets(sweet.sweet_id)}
>
AƱadir a la orden
</button>
<button
className="btn btn-danger btn-sm btn-block btn-delete"
>
Remover
</button>
</td>
</tr>
))}
</tbody>
</div>
</div>
Code for managing the inputs and the get request
const handleItemSweets = async (id) => {
set_sweet_id(id)
console.log(sweet_id)
const res = await fetch(API + '/create_sweet_item', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
sweet_quantity,
additional_instructions,
sweet_package,
sweet_id
})
})
const data = await res.json();
console.log(data);
set_sweet_quantity('');
set_additional_instructions('');
set_sweet_package('');
set_sweet_id('');
}
// Variable para guardar los dulces en una lista desde el json
const [sweets, set_sweets] = useState([]);
const getSweets = async () => {
const res = await fetch(API + "/get_all_sweets");
const data = await res.json();
set_sweets(data);
console.log(data);
};
[Error visualization][1]
[1]: https://i.stack.imgur.com/ldd1x.png
Instead of storing just one sweet_quantity state, you can convert it to an object to store all sweet_quantities:
const [sweet_quantities, set_sweet_quantities] = useState({})
and when you receive the API response, you can populate that object with the default quantity value associated with the sweet's id:
const getSweets = async () => {
const res = await fetch(API + "/get_all_sweets");
const data = await res.json();
set_sweets(data);
...
// Set default quantities in state for each item
const quantities = data.reduce((a, b) => ({ ...a, [b.sweet_id]: b.quantity}), {})
set_sweet_quantities(quantities)
};
after that you can then reference the selected state by the sweet's id and update it accordingly:
{sweets.map((sweet) => (
<tr key={sweet.sweet_id}>
<td>{sweet.s_name}</td>
<td>{sweet.s_flavor}</td>
<td>{sweet.s_price}</td>
<td>
<input
type="text"
onChange={e => set_sweet_quantities({...sweet_quantities, [sweet.sweet_id]: e.target.value})}
value={sweet_quantities[sweet.sweet_id]}
className="form-control"
/>
</td>
....
You can then repeat this logic for the other values.

How can I pass calculated amount to state in react

How can I pass currentBook, currentUnits and calculated total during on click on the Record button
Now it just displays the entered value under the purchased book section. I would like to display the data and calculated amount during on click on Record button. Could someone please advise
ie Amount = units * price
for example it should display following result 1. Mathematics 6 300
https://codesandbox.io/s/falling-grass-gwpf9?file=/src/App.js
function App() {
const [currentBook, setCurrentBook] = useState('')
const [currentUnits, setCurrentUnits] = useState('')
const [currentPrice, setCurrentPrice] = useState('')
const [currentRecord, setCurrentRecord] = useState({book:'', units:'', price:''});
const changeBook = (newBook) => {
setCurrentBook(newBook);
}
const changeUnits = (newunits) => {
setCurrentUnits(newunits);
}
const changePrice = (newprice) => {
setCurrentPrice(newprice);
}
const calculateTotal = (e) => {
var cal_total = currentUnits * currentPrice;
setCurrentRecord(currentBook, currentUnits, cal_total );
//setCurrentRecord({ ...currentRecord, [e.target.name]: e.target.value });
}
return (
<div className="App">
<div>
<h1>Online Shopping</h1>
</div>
<div className="flexbox-container">
<div className="maintransaction">
<h3>Choose a book</h3>
<div className="container">
<select defaultValue={'DEFAULT'} onChange={(event) => changeBook(event.target.value)}>
<option value="DEFAULT" disabled>Choose a book ...</option>
<option value="maths">Mathematics</option>
<option value="science">Science</option>
<option value="english">English</option>
<option value="German">German</option>
</select>
</div><br></br>
<div className="quantity">
<span className="units">
<label>Units</label>
<input name="units" type="text" onChange={(event) => changeUnits(event.target.value)}></input>
</span>
<span className="price">
<label>Price</label>
<input name="price" type="text" onChange={(event) => changePrice(event.target.value)}></input>
</span>
</div>
<div className="recordBtn">
<button onClick={(event) => calculateTotal()}>Record</button>
</div>
</div>
<div className="purchasedbooks">
<h3>Purchased book:</h3>
<table>
<tr>
<th>Item no</th>
<th>Books</th>
<th>Units</th>
<th>Price</th>
</tr>
{
//currentRecord.map(({ currentBook, currentUnits }) => (
<tr>
<td>1.</td>
<td>{currentBook}</td>
<td>10</td>
<td>250</td>
</tr>
// ))
}
</table>
</div>
</div>
</div>
);
}
export default App;
Several changes have been made in the sandbox link
Calculate the total when click record as asked in the question
Add some checking when user press the record button. Three inputs need to be filled in.
The input type should be number but not text since it may need to NaN if user enter string in the input.
Implementing a button to reset all the record.
Use map for rendering currentRecord
It looks like your map should look like this:
{currentRecord.map(item => (
<tr>
<td>1.</td>
<td>{item.currentBook}</td>
<td>{item.units}</td>
<td>{item.price}</td>
</tr>
))
}

React - List?Dictionary?State? Or am i missing something

Let's start off with the problem I'm having and telling you guys what I would like to achieve.
First of all, I'm getting this error
Warning: A component is changing a controlled input of type text to be uncontrolled.
Input elements should not switch from controlled to uncontrolled (or vice versa).
Decide between using a controlled or uncontrolled input element for the lifetime of the component.
My goal is to save data first to the state. Or I should use List, or dictionary? This is where I'm stuck. I will post my code here also for you to check what I'm doing wrong or what should I do differently.
import React from 'react'
import './TableData.css'
class TableData extends React.Component{
constructor(props){
super(props)
this.state = {
rows:[{service: '',
quantity: '',
price: '',
sum: ''}]
}
this.handleChange = this.handleChange.bind(this)
this.handleAddRow = this.handleAddRow.bind(this)
this.handleRemoveRow = this.handleRemoveRow.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleChange = idx => event => {
var rows = [...this.state.rows]
rows[idx] = {
[event.target.name]: event.target.value
}
this.setState({
rows
})
var data = this.state.rows
console.log("Log me", data)
}
handleAddRow = () => {
var item = {
service: '',
quantity: '',
price: '',
sum: ''
}
this.setState({
rows: [...this.state.rows, item]
})
}
handleRemoveRow = () => {
this.setState({
rows: this.state.rows.slice(0, -1)
})
}
handleSubmit = (event) => {
event.preventDefault()
var tableData = this.state.rows
console.log("Final data is:", tableData)
}
render() {
return (
<div className="tablePos container" >
<form onSubmit={this.handleSubmit}>
<div className="row">
<table id="tab_logic">
<thead className="tableBackground">
<tr>
<th className="col-md-auto"> Service </th>
<th className="col col-lg-2"> Quantity </th>
<th className="col col-lg-2"> Price </th>
<th className="col col-lg-2"> Sum </th>
</tr>
</thead>
<tbody>
{this.state.rows.map((item, idx) => (
<tr key={idx}>
<td>
<input className="form-control" type="text" name="service" placeholder="Ex: Cloud Service" value={this.state.rows[idx].service} onChange={this.handleChange(idx)}/>
</td>
<td>
<input className="form-control" type="text" name="quantity" placeholder="Ex: 2 Month" value={this.state.rows[idx].quantity} onChange={this.handleChange(idx)}/>
</td>
<td>
<input className="form-control" type="text" name="price" placeholder="Ex: 75.00" value={this.state.rows[idx].price} onChange={this.handleChange(idx)}/>
</td>
<td>
<input className="form-control" type="text" name="sum" placeholder="Ex: 150.00" value={this.state.rows[idx].sum} onChange={this.handleChange(idx)} />
</td>
</tr>
))}
</tbody>
</table>
</div>
<button>Send Data!</button>
</form>
<button onClick={this.handleAddRow} className="btn btn-success">Add Row</button>
<button onClick={this.handleRemoveRow} className="btn btn-danger">Delete Last Row</button>
</div>
);
}
}
export default TableData
So basically it creates 4 input boxes and then you can write in and if you are done you click Send Data it saves it to state or add new row and then it will add new row for you to input data. What I do get is the following from that code.
Console log picture of the data
It only saves the last input field data when I click send data not all of them.
Sorry about my messy explanation but I hope you did understand my problem and thank you for your replies!
while assigning the values inside onChange. You are spreading the array as needed. But you have to spread the object too.. Otherwise, it will just assign the last-changed-input-field-value to the object.
rows[idx] = {
...this.state.rows[idx],
[event.target.name]: event.target.value
};
You can find my code below.
https://codesandbox.io/s/small-dew-wjqqi

How to update many input with one handler in ReactJs?

I'm new at React, I have 3 years experience with Angular, which is why React seems strange to me. I created many input like this:
<tr>
<td>
<input value={this.state.x}
onChange={this.handleChange}/>
</td>
<td>
<input value={this.state.y}
onChange={this.handleChange}/>
</td>
<td>
<input value={this.state.z}
onChange={this.handleChange}/>
</td>
</tr>
from what I learn so far, I had to handle this input change event one by one for each <input> which I find laborious. Can I write one function to update all the input above ? Like:
handleChange = (event) => {
let obj = {};
obj[key] = event.target.value; // the key is my variable name, eg: x, y, z
this.setState(obj)
}
Does it possible to give several input a single handler function ? Thanks in advance
You can use event.target.name and event.target.value in order to update your component's as long as you set name property in each input:
this.setState({
[event.target.name]: event.target.value,
})
class App extends React.Component {
constructor() {
super()
this.state = {
x: '',
y: '',
z: '',
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value,
})
}
render() {
return (
<div>
<table>
<tr>
<td>
<input name="x" value={this.state.x} onChange={this.handleChange} />
</td>
<td>
<input name="y" value={this.state.y} onChange={this.handleChange} />
</td>
<td>
<input name="z" value={this.state.z} onChange={this.handleChange} />
</td>
</tr>
</table>
<div>Current state: {JSON.stringify(this.state)}</div>
</div>
)
}
}
ReactDOM.render(
<App />,
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>
One other way to achieve this without giving names to your inputs is to bind names of the properties in your state as arguments in your change handlers. Then in the event handler you will receive the name of the property as the first argument and the event as the second argument. Here is an example:
class Test extends React.Component {
state = {
x: '',
y: '',
z: ''
}
handleChange(name, event) {
this.setState({ [name]: event.target.value });
}
render() {
return (<table>
<tr>
<td>
<input value={this.state.x}
onChange={this.handleChange.bind(this, 'x')} />
</td>
<td>
<input value={this.state.y}
onChange={this.handleChange.bind(this, 'y')} />
</td>
<td>
<input value={this.state.z}
onChange={this.handleChange.bind(this, 'z')} />
</td>
</tr>
</table>);
}
}
This way is probably less pretty than giving names to your inputs, but it works if you for some reason don't want to have names on the inputs.
handleChange = () => {
const {value, name} = event.target
this.setState({
[name] : value
})
}

Resources