Array with inputs in React - javascript

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.

Related

Component not displaying updated data when field is updated?

I have a table that is populated with data from an array of objects.
This data is collected via websockets. The socket listens to 3 different events, and does one of the following to the array based on the event:
User can add an entry - ✅ works fully
User can modify an entry - issue
User can delete an entry - ✅ works fully
Here is the code:
interface TableEntry {
address: string;
amount: string;
children?: React.ReactNode;
}
export const TableComponent: FC = () => {
const socket = useContext(SocketContext);
const [entriesArray, setEntriesArray] = useState<TableEntry[]>([]);
const { globalAddress } =
useContext(UserDetailsContext);
useEffect(() => {
socket.on("end", (data) => {
setEntriesArray([]);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
var tempEntries = entriesArray;
socket.on("entry", (data) => {
tempEntries.push({ address: data[0], amount: data[1] });
setEntriesArray(tempEntries);
tempEntries = [];
});
return function () {
socket.off("entry");
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//Update an entry on request from a user
useEffect(() => {
var tempEntries = entriesArray;
socket.on("updateEntry", (data) => {
const findFunc = (element: TableEntry) => element.address == data[0];
const index = tempEntries.findIndex(findFunc);
tempEntries[index].address = "updated address";
setEntriesArray(tempEntries);
tempEntries = [];
});
return function () {
socket.off("updateEntry");
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
<Box>
<Box
w="427px"
h="450px"
top={"72px"}
right={"60px"}
bg={"rgba(255, 255, 255, 0.08)"}
position={"absolute"}
borderRadius="21"
overflow="hidden"
>
<TableContainer>
<Table variant="simple">
<Thead>
<Tr>
<Th fontFamily={"Orbitron"}>Address</Th>
<Th fontFamily={"Orbitron"}>Amount</Th>
</Tr>
</Thead>
<Tbody>
{entriesArray?.map((entry) => {
return entry.address == globalAddress ? (
<Tr key={entry.address} bg={"rgba(255, 255, 255, 0.05)"}>
<Td fontFamily={"Orbitron"}>{shorten(entry.address)}</Td>
<Td fontFamily={"Orbitron"}>{entry.amount}</Td>
</Tr>
) : (
<Tr key={entry.address}>
<Td fontFamily={"Orbitron"}>{shorten(entry.address)}</Td>
<Td fontFamily={"Orbitron"}>{entry.amount}</Td>
</Tr>
);
})}
</Tbody>
</Table>
</TableContainer>
</Box>
</Box>
</>
);
};
The data updates, but it is not being displayed in the table - how can I make sure the data in the table reflects the update?
I assumed since I am changing the state of entriesArray that forced a rerender - but that doesn't seem to be the case?
React's state is immutable, so you cannot update an array item directly. You need to use map with {...element} for cloning and updating that array item at the same time.
useEffect(() => {
socket.on("updateEntry", (data) => {
const tempEntries = entriesArray.map((element: TableEntry) =>
element.address == data[0]
? { ...element, address: "updated address" } //update the found element
: element //keep the original element
);
setEntriesArray(tempEntries);
tempEntries = [];
});
return function () {
socket.off("updateEntry");
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Note that, you also can clone the entire array with a spread operator like [...entriesArray] or cloneDeep before updating any element, but it's not preferable because it means you want to render the entire array (even though you want to render the updated element only) which hits performance if you have a huge array.

setState funtion is not validly updating the rows variable and hence it is not reflecting in the UI?

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>

how to update array of object in react state based on id

i'm new learning react. i have study case with basic operation (CRUD) in react. i create react saving app for CRUD expense or income.
i have three components i.e InputData for input data, TotalMoney for display total money,expense and income, and table for display all data.
*nb pemasukan=income,pengeluaran=expense, jumlah=amount
example data like this
this.state: {
items: [
{id:1, tipe:"pemasukan", jumlah:20000},
{id:1, tipe:"pemasukan", jumlah:20000}
]
}
this is image
the problem is when want to update data. like the image,when change the first data what changes is the data afterwards, not the first data. the second problem is when the type of the first data changes from income to expense, total money for total expenditure and total income not changed on display.
what is the best way for updating data in this study case?
this is my function to update data
updateItem = event => {
event.preventDefault();
const updatedTipe = this.state.tipe;
const updatedJumlah = parseInt(this.state.jumlah);
const updatedJudul = this.state.judul;
const updatedItems = Object.assign({}, this.state.items, {
tipe: updatedTipe,
jumlah: updatedJumlah,
judul: updatedJudul
});
console.log(updatedItems);
const itemLists = this.state.itemLists.map(itemList =>
itemList.id === this.state.items.id ? updatedItems : itemList
);
console.log(itemLists);
this.setState({ jumlah: 0, judul: "", itemLists: itemLists });
this.setEditing(false);
};
this is declaration table in component inputdata
<Table
items={items}
itemLists={itemLists}
editing={editing}
editItem={this.editItem}
tipe={tipe}
jumlah={jumlah}
judul={judul}
setEditing={this.setEditing}
deleteItem={this.deleteItem}
onChange={this.onChange}
updateItem={this.updateItem}
/>
this is input value for update data
<tr className="rowHover">
<td>
<select
className="form-control form-table"
name="tipe"
onChange={props.onChange}
>
<option>Pilih</option>
<option value="pengeluaran">Pengeluaran</option>
<option value="pemasukan">Pemasukan</option>
</select>
</td>
<td>
<input
type="number"
className="form-control form-table"
name="jumlah"
value={props.jumlah}
onChange={props.onChange}
/>
</td>
<td>
<input
type="text"
className="form-control form-table"
name="judul"
value={props.judul}
onChange={props.onChange}
/>
</td>
<td>
<button
onClick={props.updateItem}
type="button"
className="btn btn-success buttonStyles"
>
Update
</button>
<button
onClick={() => props.setEditing(false)}
type="button"
className="btn btn-warning buttonStyles"
>
Cancel
</button>
</td>
</tr>
this is the state
this.state = {
id: null,
judul: "",
tipe: "",
jumlah: 0,
pengeluaran: 0,
pemasukan: 0,
totalUang: 0,
items: {},
itemLists: [],
editing: false
};
this is my code
https://github.com/saldhyyoga/test
I have run your code to see what the problem is and I found a solution that works.
You have to make 2 updates:
- the first one is persisting the id of the updated item too
editItem = item => {
this.setState({
editing: true,
id: item.id, // add this line
jumlah: item.jumlah,
tipe: item.tipe,
judul: item.judul
});
};
the second one is to access the edited item data based on the persisted id
updateItem = event => {
event.preventDefault();
const updatedTipe = this.state.tipe;
const updatedJumlah = parseInt(this.state.jumlah);
const updatedJudul = this.state.judul;
const updatedId = this.state.id; // take edited item id
const updatedItems = Object.assign({}, this.state.items[updatedId], { // access the information of the editem item
tipe: updatedTipe,
jumlah: updatedJumlah,
judul: updatedJudul
});
console.log(updatedItems);
const itemLists = this.state.itemLists
.map(itemList =>
itemList.id === this.state.id ? updatedItems : itemList // replace this.state.items.id with this.state.id
);
console.log(itemLists);
this.setState({ jumlah: 0, judul: "", itemLists: itemLists });
this.setEditing(false);
};
Here is link where you can find the updated solution which is working as expected.
Edit
In order to fix the amounts calculation just invoke the amount function at the end of updateItem function:
updateItem = event => {
event.preventDefault();
const updatedTipe = this.state.tipe;
const updatedJumlah = parseInt(this.state.jumlah);
const updatedJudul = this.state.judul;
const updatedId = this.state.id;
const updatedItems = Object.assign({}, this.state.items[updatedId], {
tipe: updatedTipe,
jumlah: updatedJumlah,
judul: updatedJudul
});
console.log(updatedItems);
const itemLists = this.state.itemLists.map(itemList =>
itemList.id === this.state.id ? updatedItems : itemList
);
console.log(itemLists);
this.setState({ jumlah: 0, judul: "", itemLists: itemLists });
this.setEditing(false);
this.amount(itemLists); // add this line
};
You can also find the update at the same link

Displaying the Sum of values in React JSX

Im trying to add up all of the calories in my array thats stored in the state.
id: shortid.generate(),
text: this.state.text,
calorie: this.state.calorie
This is the data structure that being stored in the state array meals
Im currently running a forEach and using reducer to add up the values but its saying "reduce" is not a function I'm not sure what i'm doing wrong.
class App extends Component {
state = {
meals: []
};
addMeal = meal => {
this.setState({
meals: [meal, ...this.state.meals]
});
};
onDelete = id => {
this.setState({
meals: this.state.meals.filter(meal => meal.id !== id)
});
};
render() {
return (
<div className="container">
<div className="jumbotron">
<h2>Calorie Counter</h2>
<hr />
<Form onsubmit={this.addMeal} />
<table className="table table-striped">
<thead>
<tr>
<th>Meal</th>
<th>Calories</th>
<th />
</tr>
</thead>
<tbody>
{this.state.meals.map(meal => (
<Meal
key={meal.id}
meal={meal}
onDelete={() => this.onDelete(meal.id)}
/>
))}
<tr>
<td>Total:</td>
<td>
{this.state.meals.forEach(meal =>
meal.reduce(function(y, x) {
return y + x;
}, 0)
)}
</td>
<td />
</tr>
</tbody>
</table>
</div>
</div>
);
}
}
Im trying to display the total of calories inside of the meal in jsx
Reduce is an array function, not a meal object function. Try replacing the forEach with the reduce.
meals.reduce((totalCalories, meal) => totalCalories + meal.calorie, 0)
The first reduce assumes calories are numbers, the second is if strings
const meals = [
{ calorie: 10},
{ calorie: 15},
{ calorie: 20}
];
const calorieTotal = meals.reduce((totalCalories, meal) => totalCalories + meal.calorie, 0);
console.log(calorieTotal); // 45 calories
const mealsAsStrings = [
{ calorie: '11'},
{ calorie: '12'},
{ calorie: '13'}
];
const calorieStringTotal = mealsAsStrings.reduce((totalCalories, meal) => totalCalories + parseInt(meal.calorie, 10), 0);
console.log(calorieStringTotal); // 36 calories
You can't use reduce method on array elements as it's an array method. In the example above you are looping into the array and trying to call reduce with each element of array which is not right. You can do as follows -
this.state.meals.reduce((accumulator, currentValue) => accumulator + currentValue)
Hope that helps.
UPDATE -
As you are trying to calculate calories from meal object array, we can do it as follows -
this.state.meals.reduce((accumulator, currentValue)=> accumulator + accumulator, currentValue.calorie,0);
Check the link for detail use of reduce method -
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
You can use yourArray.reduce like illustrated below:
Given this array in ReactJs
const App = () => {
const course ='Half Stack application development'
const empObj =[
{
employeename: 'Ndichu Kabata',
salary: 10000
},
{
employeename: 'George Githui',
salary: 70000
},
{
employeename: 'Super Omondi',
salary: 40000
}
]
return (
<div >
<Total employees={empObj } />
</div>
);
}
and you are required to compute total salary. Do as follows:
const Total =(props) =>{
const numbers = props.employees;
const saloTotal = numbers.reduce((totalHolder,m) => totalHolder + m.salary,0);
return(
<>
<p>Total Salary: {saloTotal}</p>
</>
)}

React form is not working properly (props problem (?))

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

Categories