I've got a task to create the following project:
Everything renders good but the problem is that none of the components is working:
Cannot fill First Name and Last Name;
Select from the list is not working;
Cannot check any of the options from checklist;
When press "Submit" button - everything disappears.
It seems like I'm passing wrong values to functional components (?)
Can you explain me my mistakes so I can fix them?
Here is a link to my CodePen
The entire code:
// Lists of activities and restrictions
const optionsList = ['Science Lab','Swimming','Cooking','Painting'];
const checkList = [{id: 'a', text:'Dietary Restrictions'},
{id: 'b', text:'Physical Disabilities'},
{id: 'b', text:'Medical Needs'}]
// First Name and LastName component
function NameInput(props){
return (
<div>
<label>{props.label}</label>
<br/>
<input type ='text' value={props.value} onChange={props.handleChange} />
</div>
)
}
// List of activities component
function SelectList(props){
return (
<div>
<label>{props.label}</label>
<br/>
<select value={props.value} onChange={props.handleChange}>
{props.optionsList.map( (item, index) =>
<option key={index} value = {item}> {item} </option>)}
</select>
</div>
)
}
// CheckBox list component
function CheckBox(props){
return (
<p>
<label>{props.value}) {props.label}</label>
<input type="checkbox" name={props.name} id={props.id} value={props.value} checked={props.checked} onChange={props.handleChange} />
</p>
)
}
// CheckbBoxList component
function CheckBoxList(props){
return (
<div>
<label>{props.title}</label>
<ol>
{props.checkList.map((item,index) =>
<CheckBox
key = {index}
id = {props.id+'_'+index}
name = {props.id}
value = {item.id}
checked = {(props.applyList.indexOf(item.id) < 0 ? false : true)}
label = {item.text}
handleChange = {props.handleChange}
/>)
}
</ol>
</div>
)
}
// Remove button component
function RemoveButton(props){
return (
<button onClick= {() => props.handleClick()}>x</button>
)
}
// Report element
function ReportElem(props){
return (
<tbody>
{props.reportElem.map((item, index) =>
<tr key={index}>
<td><RemoveButton /></td>
<td>{item.firstName}</td>
<td>{item.lastName}</td>
<td>{item.activity}</td>
<td>{item.apply}</td>
</tr>
)}
</tbody>
)
}
// Report table
function ReportTable(props){
return (
<table>
<thead>
<tr>
<th>Remove</th>
<th>First Name</th>
<th>Last Name</th>
<th>Activity</th>
<th>Restrictions</th>
</tr>
</thead>
<ReportElem reportList = {props.reportList} />
</table>
)
}
class App extends React.Component{
constructor(props){
super(props)
this.state = {
firstName: '',
lastName: '',
activity: this.props.optionsList[0],
apply: [],
items: []
}
}
// Handle multiple inputs
handleChange(event){
this.setState({[event.target.name]: event.target.value})
}
// Handle CheckList changes
handleChangeChk(event){
let itemsCopy = this.state.apply
if (event.target.chacked){
itemsCopy.push(event.target.value)
} else {
console.log(itemsCopy.indexOf(event.target.value))
itemsCopy.splice(itemsCopy.indexOf(event.target.value), 1)
}
this.setState({apply: itemsCopy})
}
// Add new item to the report and refresh the initial state
addItem(){
let itemsCopy = this.state.items
itemsCopy.push({
firstName: this.state.firstName,
lastName: this.state.lastName,
activity: this.state.activity,
apply: this.state.apply.join(',')
})
this.setState({
firstName: '',
lastName: '',
activity: this.props.optionsList[0],
apply: [],
items: itemsCopy
})
}
render(){
return (
<div>
<NameInput label = 'First Name' value = {this.state.firstName} handleChange = {this.handleChange.bind(this)} />
<NameInput label = 'Last Name' value = {this.state.lastName} handleChange = {this.handleChange.bind(this)} />
<SelectList label = 'Select Activity' optionsList={this.props.optionsList} value = {this.state.activity} handleChange = {this.handleChange.bind(this)} />
<CheckBoxList title = {'Check all that apply'} checkList={this.props.checkList} applyList = {this.state.apply} handleChange = {this.handleChangeChk.bind(this)} />
<button className = 'submit' onClick = {() => this.addItem()}>Submit</button>
{this.state.items.length > 0 && <ReportTable reportList = {this.state.items} />}
</div>
)
}
}
ReactDOM.render(
<App optionsList={optionsList} checkList={checkList}/>, document.getElementById('root')
)
you need to take care of few things here
1) this.setState({[event.target.name]: event.target.value}) here you are expecting the target.name but your input has no name, so no state is updated
2) you need to pass the name to your component
<NameInput label = 'First Name' value = {this.state.firstName} name="firstName" handleChange = {this.handleChange.bind(this)} />
and in your component
<input type ='text' name={props.name} value={props.value} onChange={props.handleChange} />
So, this way you can have the name you want to pass from the state
3) your check list contains the same id
[{id: 'a', text:'Dietary Restrictions'},
{id: 'b', text:'Physical Disabilities'},
{id: 'b', text:'Medical Needs'}]
change it to
const checkList = [{id: 'a', text:'Dietary Restrictions'},
{id: 'b', text:'Physical Disabilities'},
{id: 'c', text:'Medical Needs'}]
4) in your report elem you need to have props.reportList not props.reportElem
once you apply this changes your code will look like this
// Lists of activities and restrictions
const optionsList = ['Science Lab','Swimming','Cooking','Painting'];
const checkList = [{id: 'a', text:'Dietary Restrictions'},
{id: 'b', text:'Physical Disabilities'},
{id: 'c', text:'Medical Needs'}]
// First Name and LastName component
function NameInput(props){
return (
<div>
<label>{props.label}</label>
<br/>
<input type ='text' name={props.name} value={props.value} onChange={props.handleChange} />
</div>
)
}
// List of activities componnt
function SelectList(props) {
return (
<div>
<label>{props.label}</label>
<br/>
<select name={props.name} value={props.value} onChange={props.handleChange}>
{props.optionsList.map( (item, index) =>
<option key={index} value = {item}> {item} </option>)}
</select>
</div>
)
}
// CheckBox list component
function CheckBox(props){
return (
<p>
<label>{props.value}) {props.label}</label>
<input type="checkbox" name={props.name} id={props.id} value={props.value} checked={props.checked} onChange={props.handleChange} />
</p>
)
}
// CheckbBoxList component
function CheckBoxList(props){
return (
<div>
<label>{props.title}</label>
<ol>
{props.checkList.map((item,index) =>
<CheckBox
key = {index}
id = {props.id+'_'+index}
name = {props.id}
value = {item.id}
checked = {(props.applyList.indexOf(item.id) < 0 ? false : true)}
label = {item.text}
handleChange = {props.handleChange}
/>)
}
</ol>
</div>
)
}
// Remove button component
function RemoveButton(props){
return (
<button onClick= {() => props.handleClick()}>x</button>
)
}
// Report element
function ReportElem(props){
debugger;
return (
<tbody>
{props.reportList.map((item, index) =>
<tr key={index}>
<td><RemoveButton /></td>
<td>{item.firstName}</td>
<td>{item.lastName}</td>
<td>{item.activity}</td>
<td>{item.apply}</td>
</tr>
)}
</tbody>
)
}
// Report table
function ReportTable(props){
return (
<table>
<thead>
<tr>
<th>Remove</th>
<th>First Name</th>
<th>Last Name</th>
<th>Activity</th>
<th>Restrictions</th>
</tr>
</thead>
<ReportElem reportList = {props.reportList} />
</table>
)
}
class App extends React.Component{
constructor(props){
super(props)
this.state = {
firstName: '',
lastName: '',
activity: this.props.optionsList[0],
apply: [],
items: []
}
}
// Handle multiple inputs
handleChange(event){
this.setState({[event.target.name]: event.target.value})
}
// Handle CheckList changes
handleChangeChk(event){
let itemsCopy = this.state.apply
if (event.target.checked){
itemsCopy.push(event.target.value)
} else {
console.log(itemsCopy.indexOf(event.target.value))
itemsCopy.splice(itemsCopy.indexOf(event.target.value), 1)
}
this.setState({apply: itemsCopy})
}
// Add new item to the report and refresh the initial state
addItem(){
debugger;
let itemsCopy = JSON.parse(JSON.stringify(this.state.items))
itemsCopy.push({
firstName: this.state.firstName,
lastName: this.state.lastName,
activity: this.state.activity,
apply: this.state.apply.join(',')
})
this.setState({
firstName: '',
lastName: '',
activity: this.props.optionsList[0],
apply: [],
items: itemsCopy
})
}
render(){
console.log(this.state);
return (
<div>
<NameInput label = 'First Name' value = {this.state.firstName} name="firstName" handleChange = {this.handleChange.bind(this)} />
<NameInput label = 'Last Name' value = {this.state.lastName} name="lastName" handleChange = {this.handleChange.bind(this)} />
<SelectList label = 'Select Activity' name="activity" optionsList={this.props.optionsList} value = {this.state.activity} handleChange = {this.handleChange.bind(this)} />
<CheckBoxList title = {'Check all that apply'} name="apply" checkList={this.props.checkList} applyList = {this.state.apply} handleChange = {this.handleChangeChk.bind(this)} />
<button className = 'submit' onClick = {() => this.addItem()}>Submit</button>
{this.state.items.length > 0 && <ReportTable reportList = {this.state.items} />}
</div>
)
}
}
ReactDOM.render(
<App optionsList={optionsList} checkList={checkList}/>,
document.getElementById('root')
)
Demo
Related
I've created script for dynamic form, but there's 2 things which I can't get and my head is exploding right now, hopefully somebody would help me with that.
After creating new fields - I can't remove fields depends on button which was clicked.
And after removing some of those fields, I have this error with fieldsenter image description here
import React from "react";
import {useState , useEffect} from "react";
import ReactDOM from "react-dom";
import "./index.css";
const Form = () =>{
const [fieldsLength, fieldsLengthChanger] = useState(1);
const [fields, fieldsChanger] = useState([{
id : 1,
name: "",
phone: "",
age: ""
}])
return (
<>
<div className="form__wrapper">
<h2>Form </h2>
{
fields.map((elem, index) => {
return(
<FormElement {...elem} fields={fields} fieldsChanger={fieldsChanger} fieldsLength={fieldsLength} fieldsLengthChanger={fieldsLengthChanger}/>
)
})
}
<AddMore fieldsLength={fieldsLength} fieldsLengthChanger={fieldsLengthChanger} fields={fields} fieldsChanger={fieldsChanger}/>
</div>
</>
)
}
const FormElement = ({fieldsLength ,...props}) =>{
function inputHandler(e, id){
console.log(e.target.name);
const values = [...props.fields];
values[id-1][e.target.name] = e.target.value;
props.fieldsChanger(values);
}
function removeElement(e,id){
e.preventDefault();
var arr = [...props.fields];
const newArray = arr.filter(function(elem,index){
console.log("index:" , index, "ID : ", id);
if (index + 1 != id){
return elem;
}
});
// console.log(newArray);
// arr.splice(3, 1);
props.fieldsChanger([])
props.fieldsChanger(newArray);
}
return (
<div className="group__form">
<div className="form__element">
<input type="text" value={props.fields.name} name="name" onChange={e => inputHandler(e , props.id)} />
</div>
<div className="form__element">
<input type="text" value={props.fields.phone} name="phone" onChange={e => inputHandler(e , props.id)}/>
</div>
<div className="form__element">
<input type="text" value={props.fields.age} name="age" onChange={e => inputHandler(e , props.id)}/>
</div>
{
fieldsLength > 1 ? <div className="remove__field">
<a href="#" onClick={e=>removeElement(e , props.id)}>Remove</a>
</div> : ""
}
</div>
)
}
const AddMore = (props) =>{
function addMore(){
props.fieldsLengthChanger(props.fields.length + 1);
props.fieldsChanger([...props.fields, {id:props.fields.length + 1 , name: "" , phone : "" , age :''} ]);
}
return (
<div className="add__more">
<a href="#" onClick={e=> addMore()}>Add element</a>
</div>
)
}
ReactDOM.render(<Form/> , document.getElementById("root"));
Where I'm wrong - would be really helpfull to understand what is the problem
Always use key when rendering list.
<FormElement key={elem.id} {...elem} ...
https://reactjs.org/docs/lists-and-keys.html#keys
1. You should use the props.id to remove a form instead of using index.
e.g. this must be a bug.
if (index + 1 != id) {
return elem;
}
2. And you should use id by the unique value generation instead of using array's length.
3. You should provide the key prop in the render of the form fields. Otherwise, React can't distinguish forms in rendering.
And the key should be unique whenever you add or remove items.
e.g. See the updated code. You need to use elem.id. (Of course, you should generate id in the add method.
{fields.map((elem, index) => {
return (
<FormElement
key={elem.id}
{...elem}
fields={fields}
fieldsChanger={fieldsChanger}
fieldsLength={fieldsLength}
fieldsLengthChanger={fieldsLengthChanger}
/>
);
})}
4. You could basically use a timestamp. Please use your own generation logic in the production. You can use uuid generation package.
{
id: new Date().getTime(),
name: "",
phone: "",
age: ""
}
My advices:
You don't need to use this state variable const [fieldsLength, fieldsLengthChanger] = useState(1);
Because we can get this value by fields.length.
There are a few problems in terms of components design. e.g. Please try to define addMore() in form then you don't need to pass fields, fieldsChanger as props.
Here is a full working code with your old code commented.
const Form = () => {
const [fieldsLength, fieldsLengthChanger] = useState(1);
const [fields, fieldsChanger] = useState([
{
id: new Date().getTime(),
name: "",
phone: "",
age: ""
}
]);
return (
<>
<div className="form__wrapper">
<h2>Form </h2>
{fields.map((elem, index) => {
return (
<FormElement
key={elem.id}
{...elem}
fields={fields}
fieldsChanger={fieldsChanger}
fieldsLength={fieldsLength}
fieldsLengthChanger={fieldsLengthChanger}
/>
);
})}
<AddMore
fieldsLength={fieldsLength}
fieldsLengthChanger={fieldsLengthChanger}
fields={fields}
fieldsChanger={fieldsChanger}
/>
</div>
</>
);
};
const FormElement = ({ fieldsLength, ...props }) => {
function inputHandler(e, id) {
console.log(e.target.name);
const values = [...props.fields];
const item = values.find((x) => x.id === props.id);
if (item) {
item[e.target.name] = e.target.value;
}
// values[id - 1][e.target.name] = e.target.value;
props.fieldsChanger(values);
}
function removeElement(e, id) {
e.preventDefault();
var arr = [...props.fields];
const newArray = arr.filter(function (elem, index) {
/*console.log("index:", index, "ID : ", id);
if (index + 1 != id) {
return elem;
}*/
return elem.id !== id;
});
// console.log(newArray);
// arr.splice(3, 1);
props.fieldsChanger([]);
props.fieldsChanger(newArray);
}
return (
<div className="group__form">
<div className="form__element">
<input
type="text"
value={props.fields.name}
name="name"
onChange={(e) => inputHandler(e, props.id)}
/>
</div>
<div className="form__element">
<input
type="text"
value={props.fields.phone}
name="phone"
onChange={(e) => inputHandler(e, props.id)}
/>
</div>
<div className="form__element">
<input
type="text"
value={props.fields.age}
name="age"
onChange={(e) => inputHandler(e, props.id)}
/>
</div>
{fieldsLength > 1 ? (
<div className="remove__field">
<a href="#" onClick={(e) => removeElement(e, props.id)}>
Remove
</a>
</div>
) : (
""
)}
</div>
);
};
const AddMore = (props) => {
function addMore() {
props.fieldsLengthChanger(props.fields.length + 1);
props.fieldsChanger([
...props.fields,
//{ id: props.fields.length + 1, name: "", phone: "", age: "" }
{ id: new Date().getTime(), name: "", phone: "", age: "" }
]);
}
return (
<div className="add__more">
<a href="#" onClick={(e) => addMore()}>
Add element
</a>
</div>
);
};
Im trying to create a table with an array of products, the problem I have is that the inputs take the value of any input with the same "name". Also, when I try to remove any of the products, it always removes the last one.
https://stackblitz.com/edit/react-gto9bw?file=src/App.js
const [producto, setProducto] = useState({
codigo: '',
nombre: '',
descripcion: '',
precio: '',
cantidad: '',
estado: '',
});
const [productos, setProductos] = useState([]);
const addProducto = () => {
setProductos([...productos, producto]);
};
const removeProducto = (e, index) => {
e.preventDefault();
const list = [...productos];
list.splice(index, 1);
setProductos(list);
};
const handleInputChangeProducto = (e, index) => {
e.preventDefault();
const { name, value } = e.target;
const list = [...productos];
list[index][name] = value;
setProductos(list);
};
The return its a table that has a button to add the product
return (
<div>
<table className="table-size" style={{ border: '1px solid black' }}>
<thead>
<tr>
<th scope="col">Nombre</th>
<th scope="col">Descripcion</th>
</tr>
{productos.map((producto, index) => (
<tr key={index}>
<td>
<input
name="nombre"
onChange={(e) => handleInputChangeProducto(e, index)}
/>
</td>
<td>
<input
name="descripcion"
onChange={(e) => handleInputChangeProducto(e, index)}
/>
</td>
<td onClick={(e) => removeProducto(e, index)}>
<Button>Borrar Producto {index}</Button>
</td>
</tr>
))}
</thead>
</table>
<br />
<button onClick={addProducto}>Crear Producto</button>
</div>
I've tried separating the "tr" into a separate component but that doesn't work either.
This is a common issue when using map with index instead of unique key.
So you can try like this:
Please add a global variable key in your case.
let key = 0;
Then set this key in your producto and increase it.
(In my full code, I used codigo as a key field.)
It should always be increased.
On backend API, you should get unique key as well.
Here is a full code.
import React, { useState } from 'react';
import { Button } from 'react-bootstrap';
import './style.css';
let key = 0;
export default function App() {
const [producto, setProducto] = useState({
codigo: '',
nombre: '',
descripcion: '',
precio: '',
cantidad: '',
estado: '',
});
const [productos, setProductos] = useState([]);
const addProducto = () => {
setProductos([...productos, { ...producto, codigo: ++key }]);
};
const removeProducto = (e, index) => {
e.preventDefault();
const list = [...productos];
list.splice(index, 1);
setProductos(list);
};
const handleInputChangeProducto = (e, index) => {
e.preventDefault();
const { name, value } = e.target;
const list = [...productos];
list[index][name] = value;
setProductos(list);
};
console.log({ productos });
return (
<div>
<table className="table-size" style={{ border: '1px solid black' }}>
<thead>
<tr>
<th scope="col">Nombre</th>
<th scope="col">Descripcion</th>
</tr>
{productos.map((producto, index) => (
<tr key={producto.codigo}>
<td>
<input
name="nombre"
onChange={(e) => handleInputChangeProducto(e, index)}
/>
</td>
<td>
<input
name="descripcion"
onChange={(e) => handleInputChangeProducto(e, index)}
/>
</td>
<td onClick={(e) => removeProducto(e, index)}>
<Button>Borrar Producto {index}</Button>
</td>
</tr>
))}
</thead>
</table>
<br />
<button onClick={addProducto}>Crear Producto</button>
</div>
);
}
Although the other answers are not wrong - and they all (implicitly) fixed the issues below, I wanted to call out the root cause of the problem you were seeing. It's not the use of indexes as keys (although having unique keys is a good idea for performance reasons).
When you created a new producto (at least in your original code sample), you were doing this:
const addProducto = () => {
setProductos([...productos, producto]);
};
Note the new producto being added to the array is always the same object. That means that your array contains a list of pointers that are all pointing to the same object. Modifying one will modify all of them. That's probably not what you want. Instead, do this:
const addProducto = () => {
setProductos([...productos, {...producto} ]);
};
That spreads the properties of your blank producto object onto a new object and ensures that each object in the array is really a separate thing.
The form's values were not actually controlled by the state. Going from your original code sample, make these changes:
{productos.map((producto, index) => (
<tr key={index}>
<td>
<input
name="nombre"
value={producto.nombre} // <-- this was missing. without it there is no relation between what the user is seeing in the input and what value is stored in the productos array.
onChange={(e) => handleInputChangeProducto(e, index)}
/>
</td>
<td>
<input
name="descripcion"
value={producto.descripcion} // <-- Same thing here
onChange={(e) => handleInputChangeProducto(e, index)}
/>
</td>
You need an unique key. Try to generate this unique key on your addProducto:
const addProducto = () => {
setProductos([...productos, { ...producto, id: Date.now() }]);
};
And then on your productos.map pass this generated id in your <tr key={producto.id}>.
Its recommended you have "static" indexes as key, React will understand better how manipulate your list and if occurs any modifications in this list, he will not recalculate everything.
Another point, if you need to manipulate something that depends of your previous state, like these add or remove operations, try to use functions for updates, like:
const addProducto = () => {
setProductos(prevProductos => [...prevProductos, { ...producto, id: Date.now() }]);
};
const removeProducto = (e, index) => {
e.preventDefault();
setProductos(prevProductos => [...prevProductos.slice(0, index), ...prevProductos.slice(index + 1)]);
};
Add an id field to your product JSON.
const [producto, setProducto] = useState({
id: '',
codigo: '',
nombre: '',
descripcion: '',
precio: '',
cantidad: '',
estado: '',
});
Update the ID for every product addition,
const addProducto = () => {
setProductos([...productos, {id: (prodcutos.length + 1), ...producto}]);
};
Replace your current key from index to producto.id,
{productos.map((producto, index) => (
<tr key={producto.id}>
Pass this id as param for you handleInputChangeProducto function,
<td>
<input
name="nombre"
onChange={(e) => handleInputChangeProducto(e, producto.id)}
/>
</td>
<td>
<input
name="descripcion"
onChange={(e) => handleInputChangeProducto(e, producto.id)}
/>
</td>
<td onClick={(e) => removeProducto(e, producto.id)}>
<Button>Borrar Producto {index}</Button>
</td>
const addProducto = () => {
setProductos((list) => [
...list,
{
id: list.length, //this should be unique anyway
codigo: '',
nombre: '',
descripcion: '',
precio: '',
cantidad: '',
estado: '',
},
]);
};
const removeProducto = (e, id) => {
setProductos((list) => {
list.splice(id, 1);
return [...list]
});
};
you should change your list in callback manner as shown.
The purpose of this is code is to similar to a ToDo List functionality. The add button is supposed to add another array object to the array of rows which is iterated by rows.map and reflected in the UI by showing another TextField . The subtract button is supposed to remove the selected row by sending its id as a parameter to a function called DeleteMetric(id). However, when the subtract button is clicked, its removing the last added array element irrespective of the id sent( Its sending the correct ID because I have console logged it and tested it out. ) I want that row to be removed whose id has been sent as a parameter and not the last added one. Thank you in advance for helping me.
This is how the state has been defined.
let [rows, setRows] = useState([
{
id: 1090,
name: 'Enter Name',
unit: 'Enter Unit'
},
{
id: 3000,
name: 'RISHAV',
unit: 'Unit'
}
]);[enter image description here][1]
The SubmitMetric function
const submitMetric = () => {
const c = Math.floor(Math.random() * 1000);
const newMetric = {
id: c,
name: name,
unit: unit
}
setRows([...rows, newMetric]);
}
I had the DeleteMetric function as
const deleteMetric = async (id) => {
await setRows(rows.filter((element) => {
return element.id !== id;
}));
}
It was yielding the same result so I changed the function to explicitly mutating the rows variable,which also is not working.
The DeleteMetric function now.
const deleteMetric = async (id) => {
rows = await rows.filter((element) => {
return element.id !== id;
});
await setRows(rows);
}
The returned JSX.
return (
<div>
{rows.map((row) => (
<Table>
<TableRow>
<TableCell>
<FormControl className="form-full-width">
<TextField variant="outlined" defaultValue={row.id}/>
</FormControl>
</TableCell>
<TableCell>
<FormControl className="form-full-width">
<TextField variant="outlined" defaultValue={row.unit}/>
</FormControl>
</TableCell>
<TableCell align='center'>
<IconButton onClick={submitMetric}>
<AddIcon/>
</IconButton>
</TableCell>
<TableCell align='center'>
<IconButton onClick={async () => {
await deleteMetric(row.id);
}
}>
<RemoveIcon/>
</IconButton>
</TableCell>
</TableRow>
</Table>
))}
<div align='center' className='margin-1'>
<Button variant="contained" color="primary">
Submit
</Button>
</div>
</div>
);
}
You should not mutate the array. You modify the rows state when you re-assign the filtered array inside deleteMetric(). Try the following, it uses the state hook correctly without mutating it:
const deleteMetric = (id) => {
setRows(rows.filter((el) => el.id !== id));
}
You will also need to remove every async/await in your code. Non of them is necessary.
function App() {
let [rows, setRows] = React.useState([
{
id: 1090,
name: "Name 1",
unit: "Unit 1"
},
{
id: 3000,
name: "Name 2",
unit: "Unit 2"
},
{
id: 8439,
name: "Name 3",
unit: "Unit 3"
}
]);
const submitMetric = () => {
const c = Math.floor(Math.random() * 1000);
const newMetric = {
id: c,
// only added these as I don't know what name
// and unit was in your original code
name: `Name ${c}`,
unit: `Unit ${c}`
};
setRows([...rows, newMetric]);
};
const deleteMetric = (id) => {
setRows(rows.filter((el) => el.id !== id));
};
return (
<div>
{rows.map((row) => (
<table key={row.id}>
<tbody>
<tr>
<td>
<label className="form-full-width">
<input defaultValue={row.id} />
</label>
</td>
<td>
<label className="form-full-width">
<input defaultValue={row.unit} />
</label>
</td>
<td align="center">
<button onClick={submitMetric}>Add</button>
</td>
<td align="center">
<button onClick={() => deleteMetric(row.id)}>Remove</button>
</td>
</tr>
</tbody>
</table>
))}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I'd like to, when a user deletes on row, for only that row to be deleted. Currently that only happens sometimes. And when you have only two items left to delete, when you click on the delete button, the row's data toggles and replaces itself. It doesn't actually delete.
mainCrud.js - houses the add and delete
crudAdd.js - defines state, event handlers, renders the form itself
crudTable.js - maps pre-defined rows defined in mainCrud.js, renders the table itself
Link to CodeSandbox (tables are under campaigns, dev and news tabs).
Any idea what could be causing this?
MainCrud.js
import React, { useState } from "react";
import CrudIntro from "../crud/crudIntro/crudIntro";
import CrudAdd from "../crud/crudAdd/crudAdd";
import CrudTable from "../crud/crudTable/crudTable";
const MainCrud = props => {
// Project Data
const projectData = [
{
id: 1,
name: "Skid Steer Loaders",
description:
"To advertise the skid steer loaders at 0% financing for 60 months.",
date: "February 1, 2022"
},
{
id: 2,
name: "Work Gloves",
description: "To advertise the work gloves at $15.",
date: "February 15, 2022"
},
{
id: 3,
name: "Telehandlers",
description: "To advertise telehandlers at 0% financing for 24 months.",
date: "March 15, 2022"
}
];
const [projects, setProject] = useState(projectData);
// Add Project
const addProject = project => {
project.id = projectData.length + 1;
setProject([...projects, project]);
};
// Delete Project
const deleteProject = id => {
setProject(projectData.filter(project => project.id !== id));
};
return (
<div>
<section id="add">
<CrudIntro title={props.title} subTitle={props.subTitle} />
<CrudAdd addProject={addProject} />
</section>
<section id="main">
<CrudTable projectData={projects} deleteProject={deleteProject} />
</section>
</div>
);
};
export default MainCrud;
CrudAdd.js
import React, { Component } from "react";
import "../crudAdd/crud-add.scss";
import "../../button.scss";
class CrudAdd extends Component {
state = {
id: null,
name: "",
description: "",
date: ""
};
handleInputChange = e => {
let input = e.target;
let name = e.target.name;
let value = input.value;
this.setState({
[name]: value
});
};
handleFormSubmit = e => {
e.preventDefault();
this.props.addProject({
id: this.state.id,
name: this.state.name,
description: this.state.description,
date: this.state.date
});
this.setState({
// Clear values
name: "",
description: "",
date: ""
});
};
render() {
return (
<div>
<form onSubmit={this.handleFormSubmit}>
<input
name="name"
type="name"
placeholder="Name..."
id="name"
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}
required
/>
<input
name="description"
type="description"
placeholder="Description..."
id="description"
value={this.state.description}
onChange={e => this.setState({ description: e.target.value })}
required
/>
<input
name="date"
type="name"
placeholder="Date..."
id="date"
value={this.state.date}
onChange={e => this.setState({ date: e.target.value })}
required
/>
<button type="submit" className="btn btn-primary">
Add Project
</button>
</form>
</div>
);
}
}
export default CrudAdd;
CrudTable.js
import React, { Component } from "react";
import "../crudTable/crud-table.scss";
class CrudTable extends Component {
render() {
const props = this.props;
return (
<div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">Project Name</th>
<th scope="col">Project Description</th>
<th scope="col">Date</th>
<th scope="col"> </th>
</tr>
</thead>
<tbody>
{props.projectData.length > 0 ? (
props.projectData.map(project => (
<tr key={project.id}>
<td>{project.name}</td>
<td>{project.description}</td>
<td>{project.date}</td>
<td>
<button className="btn btn-warning">Edit</button>
<button
onClick={() => props.deleteProject(project.id)}
className="btn btn-danger"
>
Delete
</button>
</td>
</tr>
))
) : (
<tr>
<td>No projects found. Please add a project.</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
);
}
}
export default CrudTable;
This is because you are filtering over projectData. Update your deleteProject method to filter over your React.useState projects variable and it will work.
const deleteProject = id => {
setProject(projects.filter(project => project.id !== id));
};
See code sandbox example here.
I tried to control dynamic checkbox rendered from my array. My problem is i have multiple checkbox but i only have one state in my constructor. Is there any others method on how to control dynamic fields/items without
Below is my code :
onAddingItem = (item) =>{
var self = this;
const value = item.target.type === 'checkbox' ? item.target.checked : item.target.value;
var newArray = self.state.product.slice();
if(item.target.checked){
newArray.push(item.target.value);
self.setState({addProducts:value, product:newArray})
} else {
newArray.splice(item.target.value, 1); //remove element
self.setState({addProducts:value, product:newArray}); //update state
}
}
render(){
var self = this;
const {title, photo, photoHeight, photoWidth, showPhoto, editorState, description, editorData, productsList} = this.state;
const product_list = productsList.map((index, i) =>
<tr key={i+1}>
<td>{i+1}</td>
<td>{index.name}</td>
<td>
<div class="checkbox checkbox-circle checkbox-color-scheme">
<label class="checkbox-checked">
<input type="checkbox" value={index.name} checked={self.state.addProducts} onChange={this.onAddingItem}/> <span class="label-text">Add ?</span>
</label>
</div>
</td>
</tr>
);
Whenever i checked one of the checkbox. All the other checkbox also being checked. As you see i want to add the value of the checkbox into an array when its checked and remove the existing value from array when the checkbox is unchecked.
It will be better if you set an isChecked property for your products array.
Here we are:
class App extends React.Component {
constructor(props){
super(props);
this.state = {
productsList :[
{name: 'USS Seawolf class', isChecked: false},
{name: 'USS Skipjack', isChecked: false},
{name: 'USS Lafayette', isChecked: false},
{name: 'USS Ohio class', isChecked: false},
]
}
}
onAddingItem = (i) => (event) => {
this.setState((state, props) => {
state.productsList[i].isChecked = !state.productsList[i].isChecked;
return {
productsList: state.productsList
}
})
}
render() {
let {productsList} = this.state;
return (
<table>
<tbody>
{ productsList.map((product, i) =>{
return(
<tr key={i+1}>
<td>{i+1}</td>
<td>{product.name}</td>
<td>
<div class="checkbox checkbox-circle checkbox-color-scheme">
<label class="checkbox-checked">
<input type="checkbox" value={product.name} checked={product.isChecked} onChange={this.onAddingItem(i)}/> <span class="label-text">Add ?</span>
</label>
</div>
</td>
</tr>
)
})}
</tbody>
</table>
)
}
}
ReactDOM.render( <
App / > ,
document.getElementById('app')
);
<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="app">
<!-- This element's contents will be replaced with your component. -->
</div>
And you can getisChecked elements by filter():
let selectedProductsArray = this.state.productsList.filter((product, i)=>{
return product.isChecked
});
You are setting checked to self.state.addProducts for each checkbox. This property expects a boolean so any truthy value will cause all of the boxes to be checked.
checked should only be true if its value is in self.state.product.
Edit: maybe something like this?
checked={self.state.product.includes(item.value)}
I agree with Emad on adding a property inside each object to control each checkbox, however make sure you don't mutate the state when updating those values.
class App extends React.Component {
constructor(props){
super(props);
this.state = {
productsList: [{ name: "Product A", isAdded: false }, { name: "Product B", isAdded: false }, { name: "Product C", isAdded: false }],
addedProducts: []
}
}
onAddingItem = (item) => {
const isChecked = item.target.checked;
const value = item.target.value;
this.setState(prevState => ({ productsList: prevState.productsList.map(product => product.name === value ? { ...product, isAdded: isChecked } : product) }));
if (isChecked)
this.setState(prevState => ({addedProducts: [...prevState.addedProducts, value] }));
else {
const newAddedProducts = this.state.addedProducts.filter(product => product !== value)
this.setState({ addedProducts: newAddedProducts });
}
}
render() {
const { productsList } = this.state;
const product_list = productsList.map((index, i) =>
<tr key={i + 1}>
<td>{i + 1} - </td>
<td>{index.name}</td>
<td>
<div class="checkbox checkbox-circle checkbox-color-scheme">
<label class="checkbox-checked">
<input type="checkbox" value={index.name} checked={this.state.productsList[i].isAdded} onChange={this.onAddingItem} /> <span class="label-text">Add ?</span>
</label>
</div>
</td>
</tr>
);
return (
<div>
<table>
<tbody>
{product_list}
</tbody>
</table>
<div style={{ marginTop: "20px" }}>Added Products: {this.state.addedProducts.join(', ')}</div>
</div>
)
}
}
ReactDOM.render( <
App / > ,
document.getElementById('app')
);
<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="app">
<!-- This element's contents will be replaced with your component. -->
</div>