Hey guys I am using this table to display data and I added a button to each row. How would I be able to hide a row when I click the hide button next to it?
I am aware of a way to do within html elements but not sure how to hide a particular row within a table thats within a loop
Can anyone show me how to accomplish this?
Thank you
import React, { Component } from 'react'
class Table extends Component {
constructor(props) {
super(props) //since we are extending class Table so we have to use super in order to override Component class constructor
this.state = { //state is by default an object
students: [
{ id: 1, name: 'Wasif', age: 21, email: 'wasif#email.com' },
{ id: 2, name: 'Ali', age: 19, email: 'ali#email.com' },
{ id: 3, name: 'Saad', age: 16, email: 'saad#email.com' },
{ id: 4, name: 'Asad', age: 25, email: 'asad#email.com' }
]
}
}
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email } = student //destructuring
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<td><button>HIDE</button></td>
</tr>
)
})
}
renderTableHeader() {
let header = Object.keys(this.state.students[0])
return header.map((key, index) => {
return <th key={index}>{key.toUpperCase()}</th>
})
}
render() { //Whenever our class runs, render method will be called automatically, it may have already defined in the constructor behind the scene.
return (
<div>
<h1 id='title'>React Dynamic Table</h1>
<table id='students'>
<tbody>
<tr>{this.renderTableHeader()}</tr>
{this.renderTableData()}
</tbody>
</table>
</div>
)
}
}
export default Table
You could add an onClick handler to the button that adds a property that determines the student should be hidden or not.
Notice the onClick={() => this.hideRow(id)} below.
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email, isHidden } = student; //destructuring
// isHidden will default to undefined if not found on the student object
// user is hidden
if (isHidden === true) {
return null;
}
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<td>
<button onClick={() => this.hideRow(id)}>HIDE</button>
</td>
</tr>
);
});
}
The hideRow method will accept a student id and will add an isHidden: true attribute to the student with that id.
hideRow(id) {
const students = this.state.students.map((student) => {
// not same id? leave as is
if (student.id !== id) {
return student;
}
return { ...student, isHidden: true };
});
this.setState({ students });
}
Now you don't want to display the isHidden column, so you have to update renderTableHeader method to skip that.
renderTableHeader() {
let header = Object.keys(this.state.students[0]);
return header.map((key, index) => {
// notice this
if (key === "isHidden") {
return null;
}
return <th key={index}>{key.toUpperCase()}</th>;
});
}
Add a isVisible key in all objects like
students: [
{ id: 1, name: 'Wasif', age: 21, email: 'wasif#email.com', isVisible: true },
{ id: 2, name: 'Ali', age: 19, email: 'ali#email.com', isVisible: true },
{ id: 3, name: 'Saad', age: 16, email: 'saad#email.com', isVisible: true },
{ id: 4, name: 'Asad', age: 25, email: 'asad#email.com', isVisible: true }
]
Then in your render row function do this
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email, isVisible } = student
return isVisible ? (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<td><button>HIDE</button></td>
</tr>
) : null
})
On button/row click update state.
Try this code
import React, { Component } from "react";
class Table extends Component {
constructor(props) {
super(props); //since we are extending class Table so we have to use super in order to override Component class constructor
this.state = {
//state is by default an object
students: [
{ id: 1, name: "Wasif", age: 21, email: "wasif#email.com", toggle: true},
{ id: 2, name: "Ali", age: 19, email: "ali#email.com", toggle: true },
{ id: 3, name: "Saad", age: 16, email: "saad#email.com", toggle: true},
{ id: 4, name: "Asad", age: 25, email: "asad#email.com", toggle: true }
]
};
}
handleClick(index) {
let students = [...this.state.students];
students[index].toggle = !students[index].toggle;
this.setState({ students });
}
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email, toggle } = student; //destructuring
if (toggle) {
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<`td`>
<button
value={index}
onClick={(e) => this.handleClick(e.target.value)}
>
Hide
</button>
</td>
</tr>
);
} else {
return null;
}
});
}
renderTableHeader() {
let header = Object.keys(this.state.students[0]);
return header.map((key, index) => {
return <th key={index}>{key.toUpperCase()}</th>;
});
}
render() {
//Whenever our class runs, render method will be called automatically, it may have already defined in the constructor behind the scene.
return (
<div>
<h1 id="title">React Dynamic Table</h1>
<table id="students">
<tbody>
<tr>{this.renderTableHeader()}</tr>
{this.renderTableData()}
</tbody>
</table>
</div>
);
}
}
export default Table;
Follow these steps:
Put an onclick on the button
Pass the array as props to the component
On the next component display the array
Add the onclick method to it which is also passed as a props from the main component(Pass id as a parameter)
In the method use a filter array to remove the row of your choice when you click it.
The code is as follow:
https://codesandbox.io/s/modern-tdd-mlmzl?file=/src/components/Table.js
Related
I have a Table component which I want to render custom component based on my data (which is an array of objects) also I need my component (I mean Table component) to have two props, one is the data object and another one is an array of objects which each object has two properties: a title and a function that render different component based on the data key. for example if the key in data object is fullName, I need to render a paragraph tag or if key is avatar, I need to return an image tag and so on.. let me show in code:
const Table = () => {
const data= [
{
id: 1,
avatar: 'blah blah blah',
fullName: 'Arlan Pond',
email: 'apond0#nytimes.com',
country: 'Brazil',
registerDate: '1/11/2021',
status: 'active',
},
];
const cols = [
{
title: 'ID',
componentToRender(rowsArr) { //this is how I defined my method.
rowsArr.map((el, index) => {
return <td>{el.id}</td>;
});
},
},
{
title: 'Avatar',
componentToRender(rowsArr) { //this is how I defined my method.
rowsArr.map((el, index) => {
return <td><span>{el.avatar}</span></td>;
});
},
},
];
return (
<div className='table-responsive' style={{width: '95%'}}>
<table className='table table-borderless'>
<thead>
<tr>
//here I need to show my headers...
{cols.map((el, index) => {
return <th key={index}>{el.title}</th>;
})}
</tr>
</thead>
<tbody>
//here I need to fill my table with components.
<tr className='table-row'>
{cols.map((el, index) => {
return el.componentToRender(data);
})}
</tr>
</tbody>
</table>
</div>
);
};
and the problem is table show only headers but data cells are empty. how can I achieve this?
This is one of the objects that you have defined.
{
title: 'ID',
componentToRender(rowsArr) { //this is how I defined my method.
rowsArr.map((el, index) => {
return <td>{el.id}</td>;
});
},
}
if you look closely , you will see that you are not returning anything from the function. The return keyword that you have used, goes back to the map method. So to fix the problem, you will need to change the code like this:
{
title: 'ID',
componentToRender(rowsArr) { //this is how I defined my method.
return rowsArr.map((el, index) => {
return el.id;
});
},
}
and tbody logic needs to change as well:
<tbody>
<tr className='table-row'>
{cols.map((el, index) => {
return <td key={index}>{el.componentToRender(rows)}</td>;
})}
</tr>
</tbody>
I refactored the component rendering. Instead of looping trough data in every component render, I created a data.map inside <tbody>, which creates a <tr> for every data entry (every object inside data array) and then renders the needed component based on the title.
Beware! title in cols should have the same naming case as the one in data. (for example both should be Avatar or avatar)
import "./styles.css";
const Table = () => {
const data= [
{
ID: 1,
Avatar: 'blah blah blah', // Capitalized like the Avatar in cols
fullName: 'Arlan Pond',
email: 'apond0#nytimes.com',
country: 'Brazil',
registerDate: '1/11/2021',
status: 'active',
},
{
ID: 2,
Avatar: 'blah blah blah2',
fullName: 'Arlan Pond',
email: 'apond0#nytimes.com',
country: 'Brazil',
registerDate: '1/11/2021',
status: 'active',
},
];
const cols = [
// refactored the components
{
title: 'ID',
component(content) { return <td>{content}</td> }
},
{
title: 'Avatar',
component(content) { return <td><span>{content}</span></td> }
},
];
return (
<div className='table-responsive' style={{width: '95%'}}>
<table className='table table-borderless'>
<thead>
<tr>
{cols.map((el, index) => {
return <th key={index}>{el.title}</th>;
})}
</tr>
</thead>
<tbody>
{/* completely changed here */}
{data.map((entry, entryindex) => {
return <tr className='table-row'>
{cols.map((el, index) => {
return el.component(data[entryindex][el.title])
})}
</tr>
})}
</tbody>
</table>
</div>
);
};
export default function App() {
return (
<div className="App">
<Table />
</div>
);
}
See it live in Codesandbox
I am trying to develop a very simple dashboard with some information. I'm trying to add in a search filter into my code so that I can narrow my data by names. In many other tutorials, I found that they have common used name.toLowerCase().indexOf(this.state.filterName.toLowerCase()) >= 0 but I just didn't work for me and would like to seek guidance from you guys.
You may also provide feedback on the general structure of my code!
Tables.js
class Table extends Component {
constructor(props){
super(props);
this.state = {
filterName: this.props.filterName,
toShow: 'all',
myArrays: [
{ 'id': 1, 'name': 'Eric', 'email': 'name1#email.com', 'role': 'student', 'isadmin': 'false' },
{ 'id': 2, 'name': 'Amanda', 'email': 'name2#email.com', 'role': 'student', 'isadmin': 'false' },
{ 'id': 3, 'name': 'Brenda', 'email': 'name3#email.com', 'role': 'staff', 'isadmin': 'true' },
{ 'id': 4, 'name': 'Charles', 'email': 'name4#email.com', 'role': 'teacher', 'isadmin': 'true' },
{ 'id': 5, 'name': 'Daimon', 'email': 'name5#email.com', 'role': 'assistant', 'isadmin': 'false' }
]
};
this.toShowAdmin = this.toShowAdmin.bind(this);
}
toShowAdmin(){
if (this.state.toShow === 'all'){
this.setState({ toShow: 'admin' }, () => console.log(this.state.toShow ))
} else {
this.setState({ toShow: 'all' }, () => console.log(this.state.toShow ))
}
}
render(){
let myArrays = []
if (this.state.toShow === 'all'){
myArrays = this.state.myArrays;
} else if (this.state.toShow === 'admin'){
myArrays = this.state.myArrays.filter(row => row.isadmin === 'true')
}
myArrays = this.state.myArrays.filter(row => {
return row.name.toLowerCase().indexOf(this.state.filterName.toLowerCase()) >= 0;
});
return(
<div>
<table className="table">
<thead className="thead-dark">
<tr>
<th scope="col"> name </th>
<th scope="col"> email </th>
<th scope="col"> role </th>
<th scope="col"> isadmin </th>
</tr>
</thead>
<tbody>
{myArrays.map(row=>
<tr key={row.id}>
<td> {row.name} </td>
<td> {row.email} </td>
<td> {row.role} </td>
<td> {row.isadmin} </td>
</tr>
)}
</tbody>
</table>
<button type='button' className='btn btn-primary' onClick={this.toShowAdmin}> Admins </button>
{ this.state.filterName }
</div>
);
}
}
export default Table;
Main.js
class Main extends Component {
constructor(props){
super(props);
this.state = {
filterName: ''
};
this.filterUpdate = this.filterUpdate.bind(this);
}
filterUpdate(value){
this.setState({ filterName: value}, () => console.log(this.state.filterName))
}
render(){
return(
<div>
<Search
filterName={this.state.filterName}
filterUpdate={this.filterUpdate}/>
<Tables
filterName={this.state.filterName}/>
</div>
);
}
}
export default Main;
You have to use componentWillReceiveProps to update your child state with parent props once the child component has been loaded. Add something like this to child component -
componentWillReceiveProps(){
this.setState({filterName: this.props.filterName})
}
See this Stack Over Flow Answer
Otherwise, if your logic allows you to use the prop directly in render, you may also do -
myArrays = this.state.myArrays.filter(row => {
return row.name.toLowerCase().indexOf(this.props.filterName.toLowerCase()) >= 0;
});
edit - use componentDidUpdate()
As rightly pointed out in comments, componentWillReceiveProps() is deprecated. Use componentDidUpdate() instead.
componentDidUpdate(){
this.setState({filterName: this.props.filterName})
}
You should use this.props.filterName directly like this:
myArrays = this.state.myArrays.filter(row => {
return row.name.toLowerCase().indexOf(this.props.filterName.toLowerCase()) >= 0;
});
I'm using react-selectable-fast link here.
How to set/get isChecked prop provided by the library.
I have two classes, Group which contains the list of items, GroupItem which is the selectable item which is wrapped in createSelectable that is provided by the library, here's my code:
import React, { Component } from 'react';
import { SelectableGroup, SelectAll, DeselectAll, createSelectable } from "react-selectable-fast";
class Group extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{ id: '1', name: 'item 1' },
{ id: '2', name: 'item 2' },
{ id: '3', name: 'item 3' },
{ id: '4', name: 'item 4' },
],
}
}
componentDidMount() {
// i want to set the the default checked items here...
// before adding createSelectable > i've done that by adding
// checked (boolean)
// for each item
}
render() {
return (
<SelectableGroup
className="main"
clickClassName="tick"
enableDeselect
allowClickWithoutSelected={true}
>
{/* I want to display the total count of selected items here*/}
<table>
<tbody>
{
this.state.items.map(item=> <GroupItem key={item.id} {...item} />)
}
</tbody>
</table>
</SelectableGroup>
);
}
}
// group item component
class GroupItem extends Component {
render() {
// props from group selector
const { name, id } = this.props;
// props from createSelectable
const { selectableRef, isSelected, isSelecting } = this.props;
return (
<tr ref={selectableRef}>
<td >
{id}
</td>
<td >
{name}
</td>
<td >
{isSelected ? 'Selected' : 'Not Selected'}
</td>
</tr>
);
}
}
export default createSelectable(GroupItem);
By wrapping GroupItem with createSelectable, how to accomplish set/get checked items. set in componentDidMount get in render
I was able to accomplish that by adding a property checked to the source of truth (items), and using the SelectableGroup handler onSelectionFinish which returns an array of selected components (refs)
<SelectableGroup ... onSelectionFinish={this.handleSelectionFinish} />
handleSelectionFinish= (obj) => {
let updatedItems = [...this.state.items];
for (let i = 0; i < updatedItems.length; i++) {
let item= updatedItems[i];
let selectedItem = obj.find(k => k.props.id === item.id)
item.checked = selectedItem !== undefined;
}
this.setState({ items: updateIitems })
}
In the following line:
checked={this.state.peopleChecked.some(({ asset}) => asset['object'] ['user']['id'] === person.userCompetences.map((user, index) => {
user['asset']['id']
})
)}
I have a problem comparing two objects.
Compares a property from the array people ->userCompetences -> asset ->id with an object from the array peopleChecked ->asset -> object ->user - > asset_id.
if id from arraypeople and asset_id, id === asset_id are equal to returnstrue. Checkbox is checked
Code here: https://stackblitz.com/edit/react-n2zkjk
class App extends Component {
constructor() {
super();
this.state = {
people: [
{
firstname: "Paul",
userCompetences: [
{ asset:{
id: "12345"
}
}
]
},
{
firstname: "Victor",
userCompetences: [
{ asset: {
id: "5646535"
}
}
]
},
{
firstname: "Martin",
userCompetences: [
{ asset: {
id: "097867575675"
}
}
]
},
{
firstname: "Gregor",
userCompetences: [
{ asset: {
id: "67890"
}
}
]
}
],
peopleChecked: [
{
amount: 0,
asset: {
id: "fgfgfgfg",
object: {
competence: null,
id: "dsdsdsdsd",
user: {
firstname: "Gregor",
asset_id: "67890"
}
}
}
},
{
amount: 0,
asset: {
id: "dsdsdsd",
object: {
competence: null,
id: "wewewe",
user: {
firstname: "Paul",
asset_id: "12345"
}
}
}
},
],
selectPeopleId: []
}
}
/*handleSelect = (person) => {
//Check if clicked checkbox is already selected
var found = this.state.peopleChecked.find((element) => {
return element.id == person.id;
});
if(found){
//If clicked checkbox already selected then remove that from peopleChecked array
this.setState({
peopleChecked: this.state.peopleChecked.filter(element => element.id !== person.id),
selectPeopleId: this.state.selectPeopleId.filter(element => element !== person.id)
}, () => console.log(this.state.peopleChecked))
}else{
//If clicked checkbox is not already selected then add that in peopleChecked array
this.setState({
selectPeopleId: [...this.state.selectPeopleId, person.id],
peopleChecked: [...this.state.peopleChecked,person]
}, () => {console.log(this.state.selectPeopleId);console.log(this.state.peopleChecked);})
}
}*/
render() {
return (
<div>
{this.state.people.map(person => (
<div key={person.id} className="mb-1">
<input
type={'checkbox'}
id={person.id}
label={person.firstname}
checked={this.state.peopleChecked.some(({ asset}) => asset['object']['user']['id'] === person.userCompetences.map((user, index) => {
user['asset']['id']
})
)}
onChange = {() => this.handleSelect(person)}
/> {person.firstname}
</div>
))}
</div>
);
}
}
Your correct checked code syntax would be below based on your data structure:
Issue was asset_id correct key was missing and map returns an array thus you would need its index, however in your case you can simply swap it with person.userCompetences.[0]['asset']['id'] but I kept your syntax in case you want it for some other purpose.
checked={
this.state.peopleChecked.some(
({ asset }) => asset['object']['user']['asset_id'] === person.userCompetences.map(
(user, index) => user['asset']['id']
)[0]
)}
However its inherently complicated and you should focus on untangling it by placing some cached const in your map function to keep track of what you are looking at. I would also advice to introduce some child component to render in the first map to make your life easier maintaining this code in the future.
Edited code: https://stackblitz.com/edit/react-ptsnbc?file=index.js
I'm able to make the filter works but simply use the filter of es6, but then the problem is I don't know how to reset the state back to the original source.
Usually the data source is an API calls but it possible to make avoid api call when the user deleted the value from the filter input?
const data = [
{
Id: "1",
FirstName: "Luke",
LastName: "Skywalker"
},
{
Id: "2",
FirstName: "Darth",
LastName: "Vader"
},
{
Id: "3",
FirstName: "Leia",
LastName: "Organa"
},
{
Id: "4",
FirstName: "Owen",
LastName: "Lars"
}
];
class App extends React.Component {
constructor() {
super()
this.state = {
data: data
}
}
filterId(e) {
const Id = e.target.value
if (Id) {
this.setState({
data: this.state.data.filter(v => v.Id === Id),
})
} else {
this.setState({
data
})
}
}
render() {
return (
<div style={styles}>
<table>
<th>Id <input type="number" onChange={e => this.filterId(e)} /></th>
<th>Name<input /></th>
{this.state.data.map((o, i) => {
return (
<tr>
<td>{o.Id}</td>
<td>{o.FirstName}</td>
</tr>
)
})}
</table>
</div>
);
}
}
https://codesandbox.io/s/oo22451v25
You may either store filtered data in separate variable or do data filtering dynamically in render function, like this.state.data.filter(v => v.Id === this.state.Id).map(...).
First up: You're going to want to filter using includes() for usability. Otherwise things won't match until they're 100% identical. This will matter once you start to deal with fields longer than one digit.
Secondly: Filtration via the render() method will allow you to search more robustly, ie allow you to backspace to unfilter, as this.state.data will remain pristine.
See below for a practical example.
// Data.
const data = [
{
Id: "1",
FirstName: "Luke",
LastName: "Skywalker"
},
{
Id: "2",
FirstName: "Darth",
LastName: "Vader"
},
{
Id: "3",
FirstName: "Leia",
LastName: "Organa"
},
{
Id: "4",
FirstName: "Owen",
LastName: "Lars"
}
]
// Filter.
class Filter extends React.Component {
// Constructor.
constructor(props) {
super(props)
this.state = {data, query: ''}
}
// Render.
render() {
return (
<div>
<table>
<tr>
<input placeholder="Query" type="number" value={this.state.query} onChange={event => this.setState({query: event.target.value})} />
</tr>
<th>Id</th>
<th>Name</th>
{this.state.data.filter((point) => point.Id.includes(this.state.query)).map((o, i) => {
return (
<tr>
<td>{o.Id}</td>
<td>{o.FirstName}</td>
</tr>
)
})}
</table>
</div>
)
}
}
ReactDOM.render(<Filter/>, document.querySelector('#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>
There are several thing you could do, but I would advice not to change the original data stored in the state, but rather add a new state property which holds the data when the original data is filtered.
You could then assign this new state object to a variable and output it this way.
It could then look somethiing like this:
const data = [
{
id: '1',
firstName: 'Luke',
LastName: 'Skywalker',
},
{
id: '2',
firstName: 'Darth',
LastName: 'Vader',
},
{
id: '3',
firstName: 'Leia',
LastName: 'Organa',
},
{
id: '4',
firstName: 'Owen',
LastName: 'Lars',
},
];
class App extends React.Component {
constructor() {
super();
this.state = {
data: data,
filteredData: null,
};
this.filterId = this.filterId.bind(this);
}
filterId(e) {
const id = e.target.value;
if (id) {
this.setState({
filteredData: this.state.data.filter(v => v.id === id),
});
} else {
this.setState({
filteredData: null,
});
}
}
render() {
const dataOutput = this.state.filteredData || this.state.data;
return (
<div>
<table>
<th>
id <input type="number" onChange={e => this.filterId(e)} />
</th>
<th>
Name<input />
</th>
{dataOutput.map((o, i) => {
return (
<tr key={o.id}>
<td>{o.id}</td>
<td>{o.firstName}</td>
</tr>
);
})}
</table>
</div>
);
}
}
Oh, and one more thing: Use camelCase. Capital letters at the beginning should only be used when declaring classes.