Filling a form dynamically in React - reactjs

I want to generate a row for each item in my list. The way i'm currently trying to achieve this is with the following:
<thead>
<tr>
<th>Title</th>
<th>Date</th>
<th>People</th>
<th>Status</th>
</tr>
</thead>
{ this.renderEvents() }
renderEvents() {
const events = this.state.data;
console.log(events);
return (
<tbody>
{ events.forEach((event) => {
return (
<tr>
<td>{event.title}</td>
<td>{event.startDate}</td>
<td>{event.userList.length}</td>
<td><Badge color="success">Coming</Badge></td>
</tr>
);
})}
</tbody>
);
}
The problem i'm currently facing is that my console.log(events) line gets executed twice and the table stays empty.
Any help is appreciated! :)

Array#forEach does not return a new array. Try Array#map instead!
return (
<tbody>
{ events.map((event) => {
return (
<tr>
<td>{event.title}</td>
<td>{event.startDate}</td>
<td>{event.userList.length}</td>
<td><Badge color="success">Coming</Badge></td>
</tr>
);
})}
</tbody>
);

Another way to do this
render(){
const data = events.map(
event => {
<tbody>
<tr>
<td>{event.title}</td>
<td>{event.startDate}</td>
<td>{event.userList.length}</td>
<td><Badge color="success">Coming</Badge></td>
</tr>
</tbody>
})
return (
<div>
{data}
</div>
)
}

More React idiomatic solution would be to use a React Component instead of calling function that returns jsx. Component is created once and mounted once, instead, function will each time return a new component (meaning the memory link will change) that will cause React reconciliation mechanism to each time mount and unmount a Component. That is not good for React performance.
I would recommend structuring your code in a next manner:
class EventViewer extends React.Component {
// keep logic of fetching events here and saving them (to state for example)
render() {
return (
<table>
<EventVieverHeader />
<EventVieverBody events={this.state.events}/>
</table>
)
}
}
const EventVieverHeader = () => (
<tr>
<td>{event.title}</td>
<td>{event.startDate}</td>
<td>{event.userList.length}</td>
<td><Badge color="success">Coming</Badge></td>
</tr>
);
const EventVieverBody = ({events}) => (
<tbody>
{events.map(
event => {
<tr>
<td>{event.title}</td>
<td>{event.startDate}</td>
<td>{event.userList.length}</td>
<td><Badge color="success">Coming</Badge></td>
</tr>
})}
</tbody>
)

Related

Loop through array using Map in React throw error

I have a functional component which is reading data from an API. I have defined an Interface but unable to loop and display in table using Map in react.
error
index.js:1 Warning: Each child in a list should have a unique "key" prop.
Interface
export interface ISiteManager{
managerId: number,
name: string,
isDeleted: false
}
React functional component return template
...
return (
<div>
<h2>EziTracker Dashboard Report</h2>
{eziStatusCollection && eziStatusCollection.length >0 && (
<table className="table">
<thead>
<tr>
<th>ManagerId</th>
<th>Name</th>
<th>Is Deleted</th>
</tr>
</thead>
{
eziStatusCollection.map((item, index) => {
return(
<tbody>
<tr key={index}>
<td>{item.managerId}</td>
<td>{item.name}</td>
<td>{item.isDeleted}</td>
</tr>
</tbody>
)})
}
</table>)}
</div>
);
};
export default MyComponent;
Your table body should be outside the map, as it's looping it each time as well:
<tbody>
{
eziStatusCollection.map((item, index) => {
return(
<tr key={index}>
<td>{item.managerId}</td>
<td>{item.name}</td>
<td>{item.isDeleted}</td>
</tr>
)})
}
</tbody>
This way the map key will be associated with each child (tr) and the error shouldn't occur.

return function not rendering table row React JS

When I log the values from snap parameter it is displaying the values there but the rows created by renderTable function aren't rendered in a browser.
function Employees() {
const rootRef = firebase
.database()
.ref()
.child("Employees");
const renderTable = () => {
var i = 0;
return rootRef.on("value", snap => {
snap.forEach(keys => {
i++;
return (
<tr key={keys}>
...
</tr>
);
});
});
};
return (
<div className={styles.Employee}>
<h1 className={styles.header}>Employees</h1>
<table>
<thead>
<tr>
...
</tr>
</thead>
<tbody>{renderTable()}</tbody>
</table>
</div>
);
}
export default Employees;
Seems like firebase's on() method invokes callback asynchronously. You must change your approach and use state and lifecycle in your React component, ie. with some help of hooks useEffect and useState as well as map method instead of forEach. In example:
function Employees() {
const [employees, setEmployees] = useState([]);
useEffect(() => {
firebase
.database()
.ref()
.child("Employees")
.on("value", setEmployees);
}, []);
const renderTable = () => {
return employees.map((employee, i) => {
return (
<tr key={keys}>
...
</tr>
);
});
};
return (
<div className={styles.Employee}>
<h1 className={styles.header}>Employees</h1>
<table>
<thead>
<tr>
...
</tr>
</thead>
<tbody>{renderTable()}</tbody>
</table>
</div>
);
}
export default Employees;
This the where rootRef is refering to
So I updated this and it kinda works.When I log The Emp attribute in JSX it contains an array of Table rows which is how it is suppose to be. When I do this it is Rendering the last node of array. and when i use .map function on it to render all of the array it shows this error TypeError: Emps.map is not a function.
How else am i suppose to render this array?
function Employees() {
const rootRef = firebase.database().ref().child("Employees")
var i = 0
const [Emps, setEmployees] = useState([])
useEffect(() => {
rootRef.on('value', snap => {
snap.forEach(keys => {
i++
setEmployees(
<tr>
<td>{i}</td>
<td>{keys.child("Name").val()}</td>
<td>{keys.child("employee_type").val()}</td>
<td>{keys.child("email").val()}</td>
<td>{keys.child("phoneNum").val()}</td>
<td>
<input className={styles.view} name="view" type="submit" value="View"/>
</td>
</tr>
)
})
})
}, [])
return (
<div className={styles.Employee}>
<h1 className={styles.header}>Employees</h1>
<table>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Employee Type</th>
<th scope="col">Email</th>
<th scope="col">Phone #</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{
Emps
}
</tbody>
</table>
</div>
)
}
export default Employees

Reactjs table row button click not working

Why in the following code Delete button click does not hit the delete method? What i am missing important thing? I am new to learn React.js
delete(e) {
console.log('Deleted');
}
static renderCatTable(Categories) {
return (
<table className='table table-striped'>
<thead>
<tr>
<th>Code</th>
<th></th>
</tr>
</thead>
<tbody>
{Categories.map(category =>
<tr key={category._id}>
<td>{category.code}</td>
<td><button onClick={this.delete} className="btn btn-danger">Delete</button></td>
</tr>
)}
</tbody>
</table>
);
}
I have defined the binding inside the constructor
this.delete = this.delete.bind(this);
The render function is given below.
render() {
let contents = this.state.loading
? <p><em>Loading...</em></p>
: Category.renderCatTable(this.state.Categories);
return (
<div>
{contents}
</div>
);
}
Because that render method is static. By definition, static methods cannot access an instance variable. You should remove that modifier if possible and it should work.

Render table when getting data asynchronous

I have some data which i get in state when componentDidMount.
I am trying render table using this data.
In my case rows not rendering.
How i can send data to tbody ?
export default class App extends Component {
constructor(props) {
super(props);
this.state={rows:null}
}
componentDidMount(){
var rows=[]
Meteor.http.call("GET", url ,function(error,result){
$.each(JSON.parse(result.content), function(key, value){
rows.push(value)
});
this.setState({
rows:rows});
})
}
renderRows(){
$.each(this.state.rows, function(d){
return(
<tr>
<td>{d[0]}</td>
<td>{d[1]}</td>
<td>{d[2]}</td>
<td>{d[3]}</td>
</tr>
)
})
}
render(){
return(
<Table>
<thead>
<tr>
<th>col1</th>
<th>col2</th>
<th>col3</th>
<th>col4</th>
</tr>
</thead>
<tbody>
{this.renderRows}
</tbody>
</Table>
)
}
}
Another option, not using JQuery and avoiding having a separate render function, is to use .map
React likes it if you have a unique key on each element in a list, so hopefully one of the fields on your row can serve this purpose.
render(){
return(
<Table>
<thead>
<tr>
<th>col1</th>
<th>col2</th>
<th>col3</th>
<th>col4</th>
</tr>
</thead>
<tbody>
{this.state.rows.map(d => (
<tr key={d[0]}>
<td>{d[0]}</td>
<td>{d[1]}</td>
<td>{d[2]}</td>
<td>{d[3]}</td>
</tr>
)}
</tbody>
</Table>
)
}
You'll also need to set your initial state for rows to be [] rather than null, in order for the first render to work.
renderRows is a function so you need to execute it. Also you will need to update that function a bit:
export default class App extends Component {
// ...
componentDidMount(){
var rows=[];
var self = this;
Meteor.http.call("GET", url ,function(error,result){
$.each(JSON.parse(result.content), function(key, value){
rows.push(value)
});
self.setState({
rows: rows
});
});
}
renderRows(){
const rows = this.state.rows || [];
return rows.map(d => {
return(
<tr>
<td>{d[0]}</td>
<td>{d[1]}</td>
<td>{d[2]}</td>
<td>{d[3]}</td>
</tr>
);
});
}
render(){
return(
<Table>
{/* ... */}
<tbody>
{this.renderRows()}
</tbody>
</Table>
)
}
}

How to get a onClick to work in a row - reactjs

I am trying to get a click even to work with a table in reactjs. My first attempt was to make the whole row clickable. Here is my code:
var UserList = React.createClass({
getInitialState: function() {
return getUsers();
},
handleClick: function(e) {
console.log("clicked");
},
render: function() {
var users = this.state.users.map(function(user) {
return (
<tr onClick={this.handleClick}>
<td>{user.name}</td>
<td>{user.age}</td>
<td></td>
</tr>
);
});
return(
<div className="container">
<table className="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Full Detail</th>
</tr>
</thead>
<tbody>
{users}
</tbody>
</table>
</div>
);
}
});
This did not work. I then tried to add a button in the table:
<button className="btn" onClick={this.handleClick}>Full Detail</button>
That also did not work. I have other onClick's working throughout my app, but how do I make this work with a table?
Your problem is the function of user that creates the table row is not bound to your react component. The value of this will not be your react component and handleClick will not exist as a property of this.
Try
var users = this.state.users.map(function(user) {
return (
<tr onClick={this.handleClick}>
<td>{user.name}</td>
<td>{user.age}</td>
<td></td>
</tr>
);}.bind(this);
});
Or use Underscore's bind if you want it to work on all browsers.
I'm new to react. How about this? You just wrap it in another function, then that function holds the closure scope and it calls it correctly.
No idea if this is bad practice or the performance difference, but it seems to work...
var users = this.state.users.map(function(user) {
return (
<tr onClick={()=>this.handleClick(user)}>
<td>{user.name}</td>
<td>{user.age}</td>
<td></td>
</tr>
);}.bind(this);
});
Binding creates a new object. Thus if you bind your function for N employees, you are inefficiently creating N new functions. A more elegant approach is to bind the function once, and pass a reference to every row. Your original code was quite close. This is what I would suggest:
handleClick = e => {
const user = this.state.users.find(u => u.uuid == e.target.dataset.uui)
console.log("clicked");
},
render() {
return(
<div className="container">
<table className="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Full Detail</th>
</tr>
</thead>
<tbody>
{this.state.users.map(user =>
(
<tr data-uuid={user.uuid} onClick={this.handleClick}>
<td>{user.name}</td>
<td>{user.age}</td>
<td>{user.details || ''}</td>
</tr>
)
)}
</tbody>
</table>
</div>
);
}
});

Resources