Dynamically Creating Table Rows With React - javascript

I am trying to create a Table using React and React-Bootstrap that has a custom number of table rows. The table is supposed to store data about player statistics of a certain video game, and based on the video game the statistics may change, thus the number of rows and titles of these rows must be able to dynamically change as well. I wanted to create an array in the state that held the list of current statistics, then map this array to a element using the map function and render the table. However, after trying several approaches I can't get any of the custom input to render. Below is the code :
Class Structure
class Statistics extends Component {
constructor(props) {
super(props)
this.state = {
game: '',
player_names: [],
positions: [],
stat_categories: [
'kills',
'deaths',
'assists'
]
}
}
renderTableRows(array) {
return (
<tr>
<th> NAME </th>
<th> TEAM </th>
<th> POSITION </th>
{ array.map(item => {
console.log(item)
<th key={item}> {item} </th>
})
}
</tr>
)
}
render() {
const columnLength = this.state.player_names.length
const statCols = this.state.stat_categories
return (
<div>
<MyNav url={this.props.location.pathname} />
<Table responsive striped bordered hover>
<thead>
{ this.renderTableRows(statCols) }
</thead>
</Table>
</div>
)
}
}
The console also properly logs the data in state (kills, deaths, assists) -- so the issue is when rendering the element. Any help would be appreciated!

You have no return statement in your map function, inside of renderTableRows.
When using ES6 arrow functions, you can either:
Return data directly without a return statement
(args) => (returnedData);
Or add some logic instead of just returning directly,
(args) => {
// Logic here
return returnedData
}
In the second case you'll need a return statement, because you are logging, if you choose to remove logging, go the first way.
Also, please post the code directly in your question, as using an image makes it less readable and not indexed by search engines.

You have to render each item in separate trs, not as a series of ths
renderTableCols(array) {
return array.map(item => <th>{item}</th>)
}
renderTableColValues(item, cols) {
return cols.map(col => <td>{item[col]}</td>)
}
renderTableRows(array) {
return array.map(item =>
<tr>
<td>{item.name}</td>
<td>{item.team}</td>
<td>{item.position}</td>
{this.renderTableColValues(item, this.cols)}
</tr>
);
}
render() {
return (
<Table>
<thead>
<tr>
<th>NAME</th>
<th>TEAM</th>
<th>POSITION</th>
{this.renderTableCols(this.cols)}
</tr>
</thead>
<tbody>
{this.renderTableRows(items)}
</tbody>
</Table>
);
}
More on tables https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table

I will give you a similar answer of what youre encoutering but its kinda different approach with a excelent solution
So, you are trying to create a dynamic table but youre making table rows static, what i did was letting the table to receive arrays of head and data and then create as many rows or datas that are required.
heres the code
export function objectIntoTableData(object) {
return Object.values(object).map((data, index) => {
return <td key={index}>{data}</td>;
});
}
You must change this index to (value,index) => , thats just my use
tableRows(data) {
return data.map(value => {
return <tr key={value.index}>{objectIntoTableData(value)}</tr>;
});
}
<thead>
<tr>
{head.map((value, index) => {
return <th key={index}>{value}</th>;
})}
</tr>
</thead>
<tbody>{this.tableRows(data)}</tbody>
Rather use a id or index inside your object since the index callback of the map function, its unsafe to use for the keys.
<ReactTableUse
head={["#", "Cell1", "Cell2", "Cell3"]}
data={[{id:1, test:1},{id:2, test:2}]}
/>

Rules:
When your state changes, render method of a class based component will be called.
Question: Who will change the state? will it grow inside the component ? What is your problem ? your are not being able to render anything ? or statistics is not dynamically rendering ? if you want to change it dynamically , you need to change the state first.

Related

React Function filter does not work (no errors in the console)

In my list, when I click on row, the background of the changes color and the id of my row is added to the array of my state. It works, but when I do the reverse my array doesn't get empty when I use the filter function (line 15).
import React, {useState} from 'react';
import './Liste.css';
import Button from '../Button/Button';
function Liste(props) {
const [nbLine, setNbLine] = useState([]);
const clickLine = (e) =>
{
if (e.target.parentNode.className.length > 0)
{
console.log(e.target.parentNode.id);
e.target.parentNode.classList.remove("lineClicked");
nbLine.filter(line => line != e.target.parentNode.id);
console.log(nbLine);
}
else
{
e.target.parentNode.classList.add("lineClicked");
nbLine.push(e.target.parentNode.id);
console.log(nbLine);
}
}
const doubleClickLine = () =>
{
console.log("doubleClickLine");
}
return (
<>
<table className='tableList'>
<thead>
<tr>
{props.headers.map((header, h) =>
<th key={h}>{header}</th>
)}
</tr>
</thead>
<tbody>
{props.records.map((record, r) =>
<tr key={r} id={props.table+"_"+record[0]} onClick={clickLine} onDoubleClick={doubleClickLine}>
{props.columns.map((column, c) =>
<td key={c}>{record[column]}</td>
)}
</tr>
)}
</tbody>
<tfoot>
<tr>
<th colSpan={7}>
{props.buttons.map((button, b) =>
<Button key={b} id={button[0]} type={button[1]} value={button[2]} click={button[3]}/>
)}
</th>
</tr>
</tfoot>
</table>
</>
)
}
export default Liste;
Here is the screen when I click (the elements are in the table).
Note: the data is fictitious.
And here is the screen when I click again (the elements resent in the array).
And here is the screen when I click again (the elements resent in the array).
Why is the filter function not working?
I unlocked myself.
By replacing the state array with an object.
const [nbLines, setNbLines] = useState({});
By using the delete operator instead of the filter method.
delete nbLines[e.target.parentNode.id];
And by assigning a value to a key instead of the push method.
nbLines[e.target.parentNode.id] = e.target.parentNode.id;

Data does not display right in table

I'm trying to display data in the table, but for some reasons appears empty fields. I think the issue is that firstly is looking deaths - fills the elements and cases becomes empty. Tried to do in many different ways. In some ways all data are just pulled in one column, in other ways the data just go in rows. Tryied to use lodash as well.
Current code:
<table className="table">
<tr>
<th>Cases</th>
<th>Deaths</th>
</tr>
{_.map(countryData, (item, key) => {
if (item.indicator === "cases") {
return (
<tr>
<td>{item.weekly_count}</td>
<td></td>
</tr>
);
} else {
return (
<tr>
<td>{item.weekly_count}</td>
</tr>
);
}
})}
</table>
Also tried in this way(still empty fields):
{countryData.map((value, key) => {
return (
<tr>
<td>
{value.indicator === "cases" ? value.weekly_count : null}
</td>
<td>
{value.indicator === "deaths" ? value.weekly_count : null}
</td>
</tr>
);
})}
My data from console.log:
Data
Expected result: expected result
Example of issue: Issue
countryData: This data comes after selecting the specific country
The map function is working just fine. The problem is with the object countryData. The object currently does not contain an array. Please make sure the object looks like:
countryData = [{indicator:"cases", weekly_count: xx}, {indicator:"deaths", weekly_count: yy}.....]
I have only used two fields inside each object, you may add more objects according to your requirement.
In order to view cases and deaths as separate columns, please add an empty cell before displaying "deaths".
{_.map(countryData, (item, key) => {
if (item.indicator === "cases") {
return (
<tr>
<td>{item.weekly_count}</td>
<td></td>
</tr>
);
} else {
return (
<tr>
<td></td>
<td>{item.weekly_count}</td>
</tr>
);
}
})}

How to get react to update when calling a method from JSX?

I have a table in my jsx that renders a league table. I want to now show the "form" of each team. i.e. the last 5 games.
I have a method that works this out (called form)
const LeagueTable = ({ teams, form }) => (
<table className="leagueTable">
<thead>
<tr className="tableRow">
<th>#</th>
<th>Name</th>
<th>P</th>
<th>W</th>
<th>D</th>
<th>L</th>
<th>F</th>
<th>A</th>
<th>GD</th>
<th>Pts</th>
<th>Form</th>
</tr>
</thead>
<tbody>
{teams.sort((a, b) => (
(b.points) - (a.points)
)).map((team, index) => (
<tr key={index} className="tableRow">
<td className="stats">{index + 1}</td>
<td className="stats">{team.name}</td>
<td className="stats">{team.won + team.lost + team.drawn}</td>
<td className="stats">{team.won}</td>
<td className="stats">{team.drawn}</td>
<td className="stats">{team.lost}</td>
<td className="stats">{team.goalsScored}</td>
<td className="stats">{team.goalsAgainst}</td>
<td className="stats">{team.goalsScored - team.goalsAgainst}</td>
<td className="stats">{team.points}</td>
<td className="stats">{form(team)}</td>
</tr>
))}
</tbody>
</table>
);
this is the method:
form = (team) => {
let lastFiveMatches;
return team && team.matches ?
lastFiveMatches = Object.values(this.props.teams.find(t => t.name === team.name).matches).sort(this.compare).reverse().slice(0, 5).map((match) => {
if(match.winner === team.name){
return 'W ';
}
else if(match.winner !== team.name){
return 'L ';
}
else {
return 'D ';
}
})
:
'---'
}
basically it all works but since adding this method to my table, it only updates my table on refresh rather than instantly (which is what it was doing before)
can anyone explain why it is doing this and how I can change it back?
when I get rid of <td className="stats">{form(team)}</td> it instantly works again so I know this method call is the problem
I have tried adding this.forceUpdate() and this.setState(this.state) to force a re-render but it looks like this causes react to blow up and I get the cannot update during an existing state transition error.
The way you are adding this form function will cause you a lot of pain.
Even though you are saying it works, the this.props... inside the function is not valid.
If you want it to work, what I would do is:
you will need to define compare and not reference this inside the function
const compare = ()=>{'whatever'}
the form code:
const form = (team, teams) => {
let lastFiveMatches;
return team && team.matches ?
lastFiveMatches = Object.values(teams.find(t => t.name === team.name).matches).sort(compare).reverse().slice(0, 5).map((match) => {
if(match.winner === team.name){
return 'W ';
}
else if(match.winner !== team.name){
return 'L ';
}
else {
return 'D ';
}
})
:
'---'
}
and now how to use it in your table:
<td className="stats">{form(team, teams)}</td>
make your functions clean and not dependent on specific this or scopes.
You could bind the specific scope, but that is not something you would like to do.
Update:
After reviewing the code, as I thought it was the scope issue
Please add a constructor to the component that contains the 'form' method and inside it
this.form = this.form.bind(this)
Or you can use ES6 instead
On table props:
<Table
teams={teams}
form={team=> this.form(team)}
/>

Dynamically add cells to table rows and dynamically create table rows with reactjs

I'm building a tbody dynamically in reactjs. I have a function that creates seed data from a loop with this:
accounts.push(
<tr key={i}>
<td>{obj.type}</td>
<td>{obj.name}</td>
<td>{obj.balance}</td>
<td>{obj.id}</td>
</tr>);
It works and i can populate rows in react with <tbody>{accounts}</tbody>. Now I'm trying to dynamically add <td>. I tried the following but it creates a string and ReactJS throws error Warning: validateDOMNesting(...): Text nodes cannot appear as a child of <tbody>:
let fields = ['type', 'name', 'balance', 'id'];
data.forEach( function(element, index) {
let fieldsLabel = `<tr key=${++i}>`;
fields.forEach( function(key, index) {
fieldsLabel = fieldsLabel.concat(`<td>${element[key]}</td>`);
});
fieldsLabel = fieldsLabel.concat(`</tr>`);
accounts.push(Array.from(fieldsLabel));
});
I'd like to do something simple like the following but it won't work because of the open <tr> tag:
data.forEach( function(element, index) {
let row = []
row.push(<tr key={++i}>)
fields.forEach( function(key, index) {
row.push(<td>{element[key]}</td>)
});
row.push(</tr>)
accounts.push(row.join(''));
});
How do you dynamically push <td> into an array so it can be used in reactjs like {accounts}?
The following will dynamically add cells to table rows and dynamically create table rows for you to use in your components.
class TbodyRow extends React.Component {
constructor() {
super()
this.createRow = this.createRow.bind(this);
}
createRow(tableRowID, data, dataOrder, cells = []) {
for (let i in dataOrder) {
cells.push(<td key={i}>{data[dataOrder[i]]}</td>)
}
return (<tr key={tableRowID}>{cells}</tr>);
}
render() {
return this.createRow(
this.props.tbodyIdKey,
this.props.rowData,
this.props.dataOrder
);
}
}
Adding data cells (<td>) to a row (<tr>): Put this inside your method that's looping over your array of data (i.e [obj, obj, obj].forEach...):
let fields = ['type', 'name', 'balance', 'id'];
accounts.push(
<TbodyRow key={obj.id}
tbodyIdKey={obj.id}
rowData={obj}
dataOrder={fields}
/>
);
Then use in your tbody as requested:
<tbody>
{accounts}
</tbody>
try this
data.map((item,i) => {
return(
<tr key={++i}>
{fields.length > 0 && fields.map((field,j) => {
return(
<td>{item[field]}</td>
)
})
</tr>
);
});
How do you dynamically push into an array so it can be used in reactjs like {accounts}?
You have the right idea but you're storing a string into fieldsLabel instead of a JSX element. Don't wrap your elements in backticks.
Don't use strings to create elements. Remember that JSX transpiles to React.createElement calls. If you create an element using strings then babel won't transpile that. Refer to the reactjs documentation to learn more about how JSX works.

Render 2 table rows in ReactJS

I want to achieve expandable row functionality for table.
Let's assume we have table with task names and task complexity. When you click on one task the description of task is shown below. I try to do it this way with ReactJS (in render method):
if (selectedTask === task.id) {
return [
<tr>
<td>{task.name}</td>
<td>{task.complexity}</td>
</tr>,
<tr>
<td colSpan="2">{task.description}</td>
</tr>
];
} else {
return <tr>
<td>{task.name}</td>
<td>{task.complexity}</td>
</tr>;
}
And it doesn't work. It says:
A valid ReactComponent must be returned. You may have returned undefined, an array or some other invalid object
I tried also to wrap 2 rows in a div but I get wrong rendering.
Please, suggest correct solution.
The render() method on a React component must always return a single element. No exceptions.
In your case, I would suggest wrapping everything inside a tbody element. You can have as many of those as you want in a table without disrupting your row structure, and then you'll always return one element inside render().
if (selectedTask === task.id) {
return (
<tbody>
<tr>
<td>{task.name}</td>
<td>{task.complexity}</td>
</tr>,
<tr>
<td colSpan="2">{task.description}</td>
</tr>
</tbody>
);
} else {
return (
<tbody>
<tr>
<td>{task.name}</td>
<td>{task.complexity}</td>
</tr>
</tbody>
);
}

Categories

Resources