I am facing an issue to order the rows of a fetched API data dynamically, i.e if the rows data within the API is changed (+/- row) to render it automatically. I am trying to print the data fetched from an API into a table. Anyhow the columns are well printed dynamically, but the function that I am using for columns this.state.columns.map(( column, index ) => doesn't function in the same way for the rows. I think i am misleading the ES6 standard, but I am not sure. Here is how it's look like, if the rows are not hardcoded.
Here is my code sample:
class App extends React.Component
{
constructor()
{
super();
this.state = {
rows: [],
columns: []
}
}
componentDidMount()
{
fetch( "http://ickata.net/sag/api/staff/bonuses/" )
.then( function ( response )
{
return response.json();
} )
.then( data =>
{
this.setState( { rows: data.rows, columns: data.columns } );
} );
}
render()
{
return (
<div id="container" className="container">
<h1>Final Table with React JS</h1>
<table className="table">
<thead>
<tr> {
this.state.columns.map(( column, index ) =>
{
return ( <th>{column}</th> )
}
)
}
</tr>
</thead>
<tbody> {
this.state.rows.map(( row ) => (
<tr>
<td>{row[0]}</td>
<td>{row[1]}</td>
<td>{row[2]}</td>
<td>{row[3]}</td>
<td>{row[4]}</td>
<td>{row[5]}</td>
<td>{row[6]}</td>
<td>{row[7]}</td>
<td>{row[8]}</td>
<td>{row[9]}</td>
<td>{row[10]}</td>
<td>{row[11]}</td>
<td>{row[12]}</td>
<td>{row[13]}</td>
<td>{row[14]}</td>
<td>{row[15]}</td>
</tr>
) )
}
</tbody>
</table>
</div>
)
}
}
ReactDOM.render( <div id="container"><App /></div>, document.querySelector( 'body' ) );
Instead, I was able to print the rows harcoded, if I give value to the 'td' elements, but I want to print it dynamically, in case the data within the API has been changed.
You are welcome to contribute directly to my Repo: Fetching API data into a table
Here is how looks like my example, when the rows values has been hardcoded within 'td' elements.
Any suggestions will be appreciated!
Just as you are iterating over columns and rows you could add an additional loop for iterating over row cells.
For example:
this.state.rows.map(row => (
<tr>{row.map(cell => (
<td>{cell}</td>
))}
</tr>
))
Note that you probably want to add a key prop:
Keys help React identify which items have changed, are added, or are
removed. Keys should be given to the elements inside the array to give
the elements a stable identity
Related
I'm trying to create a dynamic table so that every time I press the "add" button, a new row will be created.
In my example below, it creates only 1 row with all the data under its header, but with all the 3 values in this case instead of create a new row.
I'm fetching the document from Firestore and the collection includes only 1 document with an array inside, in this case with 3 values.
here is the screenshot of the table it creates:
Expecteed table example:
Code:
export default class NutritionTableOfTheUser extends Component {
constructor() {
super();
this.state = {
tableData: []
}
}
componentDidMount() {
const dbRef = collection(db, 'data');
onSnapshot(dbRef, (querySnapshot) => {
let foods = [];
querySnapshot.forEach(doc => {
foods.push(doc.data())
});
this.setState({ tableData: foods })
})
}
render() {
return (
<div className='container mt-3'>
{/* table */}
<table className='table table-hover'>
<thead>
<tr>
<th>#</th>
<th>Food</th>
<th>Quantity</th>
</tr>
</thead>
<tbody>
{this.state.tableData.map((row, index) => {
return (
<tr>
<td>{index + 1}</td>
<td>{row.value}</td>
<td>{row.quantity}</td>
</tr>
)
})}
</tbody>
</table>
</div>
)
}
}
You should check the data you are getting from firebase collection, because you might have only one document in collection which results in rendering only one row in table.
It looks like you only have a single document, and your .value and .quantity properties of the row are arrays (rather than numbers) - so doing <td>{row.value}</td> mashes all values of the array together.
Turn the .data() into the desired data format first, then set the state with it.
querySnapshot.forEach(doc => {
const [obj] = doc.data();
const tableData = obj.value.map((value, i) => ({
value,
quantity: obj.quantity[i]
}));
this.setState({ tableData })
});
I'm using React to sort table using underscore.js, but the sorted data is added to the bottom of the table along with the original data. I need to just render the sorted table in my component. What is the correct way to achieve this?
class Players extends React.Component {
constructor() {
super();
this.state = { players:[]};
}
componentDidMount() {
fetch('http://localhost:5000/players')
.then((data) => data.json())
.then((data) => this.setState( { players: data } ));
}
sortByCode = () => {
this.setState( { "players": _.sortBy(this.state.players,'TEAMCODE')});
}
render() {
return (
<table border={1}>
<tbody>
<tr>
<th onClick={this.sortByCode}>Code</th>
<th>Name</th>
</tr>
{this.state.players.map( (item) => {
return
<tr key={item.TEAMCODE}>
<td><img height={20} width ={20}
alt="pics" src={'/logos/' + item.TEAMCODE + '_logo.svg'}></img>
</td>
<td>{item.TEAMCODE}</td>
<td>{item.NAME}</td></tr>;
})}
</tbody>
</table>
)};
It's not recommended to use the index as a key (see ReactJS documentation)
I would advise to find the unique key of your dataset that seems to be the combination of TEAMCODE and NAME so you should replace your code with :
<tr key={item.TEAMCODE+item.NAME}>
I am trying to populate table data by importing json. However, getting below error:
Uncaught Invariant Violation: Objects are not valid as a React child (found: object with keys { list }). If you meant to render a collection of children, use an array instead.
Below is the code I am using:
import reg from "./data/reg.json";
class TableRow extends Component {
render() {
const { data } = this.props;
const list = data.map(adata => {
return (
<tr>
<React.Fragment>
<td key={adata.FName}>{adata.FName}</td>
<td key={adata.LName}>{adata.LName}</td>
<td key={adata.Age}>{adata.Age}</td>
</React.Fragment>
</tr>
)//return
})//regionslist
return (
{ list }
);//return
} //render
} //class
class DTable extends Component {
render() {
return (
<Table striped bordered hover>
<TableHeader />
<tbody>
<TableRow data={this.props.data} />
</tbody>
</Table>
);
}
}
class DataTable extends Component {
render() {
return (
<DTable data={reg} />
);//return
} //render
}
Problem
I recreated your setup trying to reproduce your error. You can see what I reproduced here: Stackblitz. The issue seems to lie in the fact that your map creates some table rows, but there is no parent element to wrap them, which is required by JSX.
Solution
This can be fixed by wrapping {list} inside of a React.Fragment:
class TableRow extends Component {
render() {
const { data } = this.props;
const list = data.map(adata => {
return (
<tr>
<td key={adata.FName}>{adata.FName}</td>
<td key={adata.LName}>{adata.LName}</td>
<td key={adata.Age}>{adata.Age}</td>
</tr>
)//return
})//regionslist
return (
<React.Fragment>
{ list }
</React.Fragment>
);//return
} //render
} //class
Adding the React.Fragment around the list items resolved the issue. You can see the working solution here: Stackblitz
Alternatives
As of React 16.2 (Read more) you also have the option to return empty JSX tags instead of typing out React.Fragment. The solution above would look like this:
class TableRow extends Component {
render() {
const { data } = this.props;
const list = data.map(adata => {
return (
<tr>
<td key={adata.FName}>{adata.FName}</td>
<td key={adata.LName}>{adata.LName}</td>
<td key={adata.Age}>{adata.Age}</td>
</tr>
)//return
})//regionslist
return (
<>
{ list }
</>
);//return
} //render
} //class
Lastly it is also possible to return an array of elements since React 16.0. If you would choose to do this the above code would look like this then:
class TableRow extends Component {
render() {
const { data } = this.props;
const list = data.map(adata => {
return (
<tr>
<td key={adata.FName}>{adata.FName}</td>
<td key={adata.LName}>{adata.LName}</td>
<td key={adata.Age}>{adata.Age}</td>
</tr>
)//return
})//regionslist
return [list];//return
} //render
} //class
A limitation of this option is however that a key has to be added to the items as React will otherwise throw this warning in the console:
Warning: Each child in a list should have a unique "key" prop.
Note that even if you omit this key it will still render properly.
Initially, there was a limitation in React that you can only return a single element from render function but it has been changed after React 16, Now you can return the array of elements.
In your case, you are trying to array of the element without mentioning that it's an array due to which you have to group it using Fragment.
This issue can be solved in two ways:
Wrap array inside Fragment or any other container element (But in that case it will add one extra node to the DOM which is bad and to solve that issue Fragment's are introduced. for more details check here: https://reactjs.org/docs/fragments.html)
return (
<React.Fragment>
{ list }
</React.Fragment>
);
Return an array.
return ([ list ]);
I am learning programming and have an issue. I would like to iterate and loop (i think are the terms) a row. The row will contain 3 columns who's data comes from a REST api. Once the row has 3 columns fulled, I would like a new row to be created with 3 more columns.
What is the easiest way to achieve this thanks.
My code is here https://jsfiddle.net/Ory4n/8k7dabyr/1/ and I found a possible solution code here https://jsfiddle.net/dya52m8y/2/ however I do not know how to implement that code into my own project as of yet.
class Test extends Component {
constructor(){
super();
this.state = {
pages: []
}
}
componentDidMount() {
let pagesURL = "http://creartem.nz/wp-json/wp/v2/projects";
fetch(pagesURL)
.then(response => response.json())
.then(response => {
this.setState({
pages: response
})
})
}
render() {
let pages = this.state.pages.map((page, index) => {
return (
{/* Start Loop */}
<div key={index}>
<h2>{page.title.rendered}</h2>
<p>{page.acf.technologies}</p>
<p>{page.acf.team}</p>
</div>
{/* end Loop */}
)
})
return (
<section>
{pages}
</section>
);
}
}
export default Test;
If you want to render an array of components, you must call a function that in turn returns a call to the .map() method you had specified. See below:
render() {
const renderPages = () => {
return this.state.pages.map((page, index) => {
return (
<div key={index}>
<h2>{page.title.rendered}</h2>
<p>{page.acf.technologies}</p>
<p>{page.acf.team}</p>
</div>
);
})
}
return (
<section>
{renderPages()}
</section>
);
}
I could be wrong, but if I understand correctly, you would like to create a new row containing a h2 and two p tags. You could try using a table to achieve this output. Something like:
const renderPages = () => {
return (
<div>
<table>
<tr>
<th></th>
<th></th>
<th></th>
</tr>
{this.state.pages.map(page => {
return (
<tr key={page.id}>
<td><h2>{page.title.rendered}</h2></td>
<td><p>{page.acf.technologies}</p></td>
<td><p>{page.acf.team}</p></td>
</tr>
)
})}
</table>
</div>
)
}
Also, avoid using index as your key. It causes weird issues. Use something like an id.
Here is a similar example on codepen: https://codepen.io/mkempinsky/pen/gBapqg?editors=1111
I create table that render data which receive from server. But in this table I have cols which not all user should to see. This is my code:
class TableBody extends React.Component {
state = {
columns: {
id: props => (
<td key="id">
{props._id}
</td>
),
title: props => (
<td key="title">
{props.title}
</td>
),
manager: props => (
<td key="manager">
{props.manager}
</td>
)
},
hiddenColumns: {
user: ['manager']
}
}
In state I init my columns and add columns restrictions for user (he can not see manager column). In render I do next:
render() {
const hiddenColumns = this.state.hiddenColumns[this.props.role] || [];
const columns = Object.keys(this.state.columns).filter(key => {
return hiddenColumns.indexOf(key) === -1
});
return this.props.companies.map(company => (
<tr key={offer._id}>
{columns.map(element => this.state.columns[element](company))}
</tr>
));
}
I get hidden columns for current user and filter key in columns. After this I use map to go over data which receive from server and inside map I go over for each filtered columns and send element (props).
In the future, more columns will be added to this table and make this:
{columns.map(element => this.state.columns[element](company))}
will not be effective.
Maybe I can create main template and after init remove columns which user should not to see, but I don't know how.
Please help me
Thank you
I think you are doing this completely wrong. you should never filter data specific to user role on client side.
Ideally such data should be filtered on server side and then send to client only role specific data.
With your current approach user can simply inspect browser network tab and read all other restricted columns.