I'm making a program that posts train schedules. It's far from done, but I'm trying to implement an "X" button that removes trains from the list right now.
I heard React Components update every time you change their state. So why isn't the table remapping with one less train? The deletion shows up in the console, just not on the table.
function TrainPage(){
const trains =[
{name: "Thomas",
dest: "New York",
first: "05:00",
freq: 30},
{name: "Duncan",
dest: "Boston",
first: "06:00",
freq: 45}
]
const [trainList, setList] = useState(trains);
function remove(event){
console.log(event.target.id)
let train2delete = event.target.id;
trains.splice(train2delete, 1);
console.log(trains)
setList(trains);
console.log(trainList);
//but this doesn't cause the state to rerender. Why?
}
return(
<div>
<table>
<tr>
<th>Train Name</th>
<th>Destination</th>
<th>Frequency</th>
<th>Minutes Away</th>
<th>Next Arrival</th>
</tr>
{trainList.map((train, index) => (
<tr>
<td>{train.name}</td>
<td>{train.dest}</td>
<td>{train.freq}mins</td>
<td></td>
<td></td>
<td><button onClick = {remove} data-toggle='tooltip' data-placement='left' title='delete train' id = {index}>x</button></td>
</tr>
))}
</table>
It would help if you consider the following pointers:
you need to provide the key prop when generating an array of JSX elements(Click here for more information).
When possible avoid using indexes as values for the key prop
whenever you update a piece of state that is an Object, you need to provide a brand new Object (Click here for more information).
function TrainPage(){
const [trainList, setList] = useState([
{name: "Thomas",
dest: "New York",
first: "05:00",
freq: 30},
{name: "Duncan",
dest: "Boston",
first: "06:00",
freq: 45}
]);
function remove(event){
const trainName = event.target.value;
// filter method results in a brand new array;
setList(trainList.filter(entry => entry.name !== trainName)));
}
return (
<div>
<table>
<tr>
<th>Train Name</th>
<th>Destination</th>
<th>Frequency</th>
<th>Minutes Away</th>
<th>Next Arrival</th>
</tr>
{/* using train.name as key value assuming that is unique. if not, please use/generate a unique string */}
{trainList.map((train) => (
<tr key={train.name}>
<td>{train.name}</td>
<td>{train.dest}</td>
<td>{train.freq}mins</td>
<td></td>
<td></td>
<td><button
onClick = {remove}
data-toggle='tooltip'
data-placement='left' title='delete train'
value={train.name}>x</button></td>
</tr>
))}
</table>
</div>
)
}
Related
I am learning React, and I been stuck here for a while now, the Middle.js component and table. I am trying to loop over array of objects from the data I am fetching. It is userPools array and display in a table poolProvidersname and then consequently rest of the data.
I am quite confused as to how do I do this?
Here is the the sandbox: https://codesandbox.io/s/naughty-ellis-xrir4?file=/src/components/Table.js
Thank you.
jumping from JS to markup on JSX can bit a bit confusing at first
When iterating an array of objects for display purposes, what you want to do is return a JSX fragment for each item on your array.
So if you have an array that looks like this:
var data = [{ id: 1, name: 'Lucia', age: 20 }, { id: 2, name: 'Karina', age: 21 }, { id: 3, name: 'Maria', age: 22 }];
to display it in your component you would go like this:
const Table = ({ data }) => {
if (!Array.isArray(data)) return null;
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
{
data.map(item => // notice how we jump straight back to JSX here
<tr key={item.id}>
<td>
{item.name}
</td>
<td>
{item.age}
</td>
</tr>)
}
</tbody>
</table>
);
};
Please clarify my problem of toggle for specific row of the table with react hooks. whenever I press click.. it opens for every row and by default the table head is moving right it is not constant.
Error in toggleHidden(key). How to correct my toggleHidden function?
const[isHidden , setIsHidden] = React.useState(true)
const toggleHidden = () => setIsHidden(!isHidden)
const data = [
{
"name": "gvf",
"email": "abc",
"companyname": "xyz",
"address": "abcy"
},
{
"name": "abi",
"email": "dhf",
"companyname": "dhd",
"address": "fhfh"
}
]
return (
<div>
<Row>
<Col>
<table className="table table-hover table-striped table-sm">
<thead className="thead-dark">
<tr>
<th>Name</th>
<th>Email</th>
<th>CompanyName</th>
<th>Address</th>
</tr>
</thead>
<tbody>
{data.map((a , key) => (
<tr key={a.name}>
<td><Button onClick = {toggleHidden(key)}>Click</Button>
{!isHidden && <p>Hello ABIII</p> }
</td>
<td>{a.name}</td>
<td>{a.email}</td>
<td>{a.address}</td>
<td>{a.companyname}</td>
</tr>
))}
</tbody>
</table>
</Col>
</Row> </div>
you need define the specific row - when You mapping:
{data.map(a => (
)}
try add key attribute to every item in collection like this:
{data.map((a, key) => (
))}
then pass to Your item:
<tr key={key}>
So now every is unique - so if You pass that key to your function:
<Button onClick = {toggleHidden(key)}>
the program should know which specific item execute toggleHidden function
import React, { Component, Fragment } from "react";
import "./App.css";
export default class App extends Component {
constructor(props){
super(props)
this.state = {
data: [
{
id: 1,
Firstname: "Jill",
Lastname: ["john", "hobss", "smith"],
Age: [1, 2, 3],
company: ["facebook", "google", "netflix"],
skills: ["python", "java", "scala"]
},
{
id: 2,
Firstname: "Jill",
Lastname: ["john", "hobss", "smith"],
Age: [1, 2, 3],
company: ["facebook", "google", "netflix"],
skills: ["python", "java", "scala"]
},
{
id: 3,
Firstname: "Jill",
Lastname: ["john", "hobss", "smith"],
Age: [1, 2, 3],
company: ["facebook", "google", "netflix"],
skills: ["python", "java", "scala"]
},
{
id:4,
Firstname: "Jill",
Lastname: ["john", "hobss", "smith"],
Age: [1, 2, 3],
company: ["facebook", "google", "netflix"],
skills: ["python", "java", "scala"]
}
]
}
}
handleChange = (id, company, event) => {
const data = this.state.data;
for(let d of data){
if(d.id === id){
for(let c of d.company){
if(c === company){
c = event.target.value
}
}
}
}
}
render() {
return (
<div>
<table>
<tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Age</th>
<th>company</th>
<th>skills</th>
</tr>
{
this.state.data.map(td => {
return (
<tr>
<td>{td.Firstname}</td>
<td>
<table>
<tr>
<td>{td.Lastname[0]}</td>
<td>{td.Lastname[1]}</td>
<td>{td.Lastname[2]}</td>
</tr>
</table>
</td>
<td>
<table>
<tr>
<td>{td.Age[0]}</td>
<td>{td.Age[1]}</td>
<td>{td.Age[2]}</td>
</tr>
</table>
</td>
<td>
<table>
<tr>
<td>
<input type="text" value={td.company[0]} onChange={(e) => this.handleChange(td.id, td.company[0], e)} />
</td>
<td>
<input type="text" value={td.company[1]} onChange={(e) => this.handleChange(td.id, td.company[1], e)}/>
</td>
<td>
<input type="text" value={td.company[2]} onChange={(e) => this.handleChange(td.id, td.company[2], e)}/>
</td>
</tr>
</table>
</td>
<td>
<table>
<tr>
<td>{td.skills[0]}</td>
<td>{td.skills[0]}</td>
<td>{td.skills[0]}</td>
</tr>
</table>
</td>
</tr>
)
})
}
</table>
</div>
)
}
}
Here i am trying to update my table using input data .
I am sending data using event to handleChange() function and changing data of state.
But It is not working please have a look.
I am not able to change input value also.
Please have a look
if any way to solve this issue.
Thanks
I am sending data using event to handleChange() function and changing data of state.
But It is not working please have a look.
I am not able to change input value also.
Please have a look
if any way to solve this issue.
Thanks
You are currently mutating state which is an anit-pattern in react. In order to update state you need to call this.setState(newState) which will then trigger your component to rerender with the new state values;
You should replace the value attribute of the input with defaultValue so it can be changed in the first place, then use setState in your handleChange method to update the data.
how to map subarray in react based on key of parent array?
I have tried mapping by id key of main array to map elements of dish_count array
<TableCell align="left">
{this.state.persons.map((items,name) =>
<div key={this.state.persons.id}>
{(typeof(items.dish_count)=="object")? (<div>
{ items.dish_count.map((subdata) =>
<table>
<td >
{subdata.meal_type}
</td>
</table>
)
}
</div>): (null)}</div>)}
</TableCell>
<TableCell align="left"> {this.state.persons.map((items,name) =>
<div key={this.state.persons.id} >{(typeof(items.dish_count)=="object")? (<div>
{
items.dish_count.map((subdata) =>
<table>
<td >
{subdata.dish_count}
</td>
</table>
)
}
</div>): (null)}</div>)}</TableCell>
i want to map subarray dish_count by key id of parent array .I am able to map but the mapping is multiple and is not exclusive by parent array key.dish_count is the subarray of package array
persons array
"data": [
{
"id": 1,
"name": "Gold",
"dish_count": [
{
"dish_count": 4,
"meal_type": "Starters"
},
{
"dish_count": 4,
"meal_type": "Main Course"
},
{
"dish_count": 4,
"meal_type": "Dessert"
},
{
"dish_count": 4,
"meal_type": "Lunch"
}
]
},
{
"id": 2,
"name": "Basic",
"dish_count": [
{
"dish_count": 2,
"meal_type": "Starters"
},
{
"dish_count": 2,
"meal_type": "Main Course"
},
{
"dish_count": 2,
"meal_type": "Dessert"
},
{
"dish_count": 2,
"meal_type": "Lunch"
}
]
}
]
I want
Meal Type No of Dishes
Gold Starters 4
Main Course 4
Desert 4
Lunch 4
Basic Starters 2
Main Course 2
Desert 2
Lunch 2
You have a few problems in your code:
this.state({persons:''});: This will set the initial persons to be an empty string and will fail with map.
console.log('package',this.state.persons): setState is async and the console.log will not print the desired state but the previous state. Use the callback of setState as second parameter to access the new state:
this.setState({persons:res.data.data.data}, () => console.log('package',this.state.persons) );
this.state.persons.map((items, name) =>: The map function will provide different parameters: the first is the person and the second is the index of that person within the array.
div key = {this.state.persons.id}: since persons is an array, the key will be undefined. If you use the map function correctly, you can use person.id.
When you fixed these problems, the code should work as expected.
To only show the expected dishes per person and not to print the duplication you have to write it like this:
class Table extends React.Component {
render() {
return <table>
<thead>
<tr>
<td>ID</td>
<td>Name</td>
<td>Meal Type</td>
<td>Number of Dishes</td>
</tr>
</thead>
<tbody>
{this.state.persons.map(person =>
<tr>
<td>{person.id}</td>
<td>{person.name}</td>
<td>
<table>
<tbody>
{person.dish_count.map(dish => <tr>
<td>{dish.meal_type}</td>
</tr>
)}
</tbody>
</table>
</td>
<td>
<table>
<tbody>
{person.dish_count.map(dish => <tr >
<td>{dish.dish_count}</td>
</tr>
)}
</tbody>
</table>
</td>
</tr>
)
}
</tbody>
</table >
}
}
By not iterating over the person for each sub-table, you can remove the duplicated data.
codepen
Hope this helps.
I am struggling in writing simple logic.
Please have a look and see if you can help me.
I have following code which shows three tables on UI at the moment based on data im returning from server.
I have search box which filter data in each table. It is also working fine.
My requirement:
I need to show one generic message "No data found" when no data found in all three tables based on input.
<div>
<input type="text" ng-model="searchStudent">
<div>
<div ng-repeat="studentPermissions in studentPermissions">
<div ng-repeat="student in studentPermissions.entities">
<table>
<thead>
<tr>
<td >Student</td>
<td>{{student.StudentName}}</td>
</tr>
<tr>
<th></th>
<th ng-repeat="permission in student.entityStudents[0].userPermissions"><div><span>{{permission.Name}}</span></div></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="student in student.entityStudents | filter:searchStudent">
<td>{{student.FirstName}} {{student.LastName}}</td>
<td ng-repeat="permission in student.userPermissions">
<input ng-model="permission.Checked" type="checkbox">
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
I have written search method in controller.
$scope.search = function (item) {
if ($scope.searchUser == null || $scope.searchStudent.trim() == "")
return true;
if (item.FirstName.toLowerCase().indexOf($scope.searchStudent.toLowerCase()) != -1 || item.LastName.toLowerCase().indexOf($scope.searchStudent.toLowerCase()) != -1 || item.Role.toLowerCase().indexOf($scope.searchStudent.toLowerCase()) != -1) {
return true;
}
return false;
};
I tried by setting flag on /off tricks but each time ended up something wrong.
My Sample Data:
$scope.studentPermissions = [
{entities[{StudentName: 'Tester', entityStudents[{FirstName: 'Test0', LastName: 'Test0', userPermissions[{prop 1, prop2, prop3}]}]}]},
{entities[{StudentName: 'Tester', entityStudents[{FirstName: 'Test1', LastName: 'Test2', userPermissions[{prop 1, prop2, prop3}]}]}]},
{entities[{StudentName: 'Tester', entityStudents[{FirstName: 'Test3', LastName: 'Test4', userPermissions[{prop 1, prop2, prop3}]}]}]},
]
You can use ngIf/ngShow/ngHide/ngClass for this, but you have to slightly change the way you write your repeat. For example, you can write it like this:
ng-repeat="student in filteredData = (student.entityStudents | filter:searchStudent)">
You can also do:
ng-repeat="student in student.entityStudents | filter:searchStudent as filteredData"
So, now you have a reference to the filteredData and you can just use filteredData.length in your ngShow/Hide/If/Class as the boolean. If it's length is 0 it will be falsy:
<p ng-hide="filteredData.length">No Data Found</p>
Here's a working demo, which I think matches your requirements, as best I can tell. Note that I moved your filter to the first nested repeat so that the tables were fully hidden if they didn't match and you only have one "No Data Found" message.
Updated Plunker