I'm using Datatable Library to draw table easily.
And I got a data with Fetch API and render to table and It works well. But I don't know why DataTable Funcions like sorting, searching, showing options.
As you see, get data from API and render to HTML are works well, but when I click sort or search function it changes to this.
Also Another functions like interval the data from API every 10 seconds and render to table are works well.
It seems that there are some problem in initial state.
import React, { Component } from 'react';
import './PostContainer.css';
class PostContainer extends Component {
constructor(props) {
super(props);
this.state = {
tableData: {
status: '0000',
data: {
loading: { sell_price: 'loading', volume_7day: 'loading' },
},
},
};
}
async componentDidMount() {
this.getData();
this.interval = setInterval(() => {
this.getData();
}, 10000);
}
getData() {
fetch('https://api.bithumb.com/public/ticker/all')
.then(res => {
const data = res.json();
return data;
})
.then(res => {
this.setState({
tableData: res,
});
})
.catch(error => {
console.error(error);
});
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
let data = this.state.tableData;
let chart = [];
console.log(data);
if (data.status === '0000') {
delete data.data['date'];
for (let [key, value] of Object.entries(data.data)) {
chart.push(
<tr key={key}>
<td>{key}</td>
<td>{value.sell_price}</td>
<td>{value.volume_7day}</td>
</tr>
);
}
} else if (
data.status === '5500' ||
data.status === '5600' ||
data.status === '5900'
) {
this.setState({
tableData: {
data: {
ERROR: {
sell_price: 'ERROR with API',
volume_7day: 'ERROR with API',
},
},
},
});
}
return (
<div className="Post">
<table id="table" className="table table-striped table-bordered">
<thead>
<tr>
<th>Coin Name</th>
<th>Current Price</th>
<th>Volume</th>
</tr>
</thead>
<tbody>{chart}</tbody>
</table>
</div>
);
}
}
export default PostContainer;
Can access to DEMO directly. I uploaded to Github Pages.
I can think of 2 issue to look for
a.
If you see in screenshot there is no Pagination.
When I try load DEMO with Developer Console open.
It works fine and you will see Pagination which will show 10 record at a time.
Check your code for Datatable initialization code in index.js
$('#table').DataTable({
order: [[1, 'desc']],
});
Make sure above code is called after Data is loaded in HTML
b.
Your state value is not updated to actual values while sorting
tableData: {
status: "0000",
data: {
loading: {
sell_price: "loading",
volume_7day: "loading"
}
}
}
Related
New programmer here learning ReactJS. I have some code that uses axios to make an HTTP request to get XMLData from a local file. Then on the response, I am using xml-js to turn that XML data into a JSON object. Then, I am taking that jsonObj and saving it to a state using setState().
I have a function renderTableRows() that is supposed to return JSX to display my JSON data on the browser. I destructured the state and try to console log from the renderTableRows() but when I try to access users.result.body I get
"TypeError: Cannot read property 'body' of undefined".
When I do it from the then() within the componentDidMount() I am able to access the data. I have also include an excerpt of the data I am reading at the bottom of the code.
I'd like to iterate using map() through all the row array attributes. Any help would be appreciated.
class Table extends Component {
constructor(props) {
super(props)
this.state = {
users: []
}
}
async componentDidMount() {
axios.get(XMLData, {
"Content-Type": "application/xml; charset=utf-8"
})
.then((response) => {
var jsonObj = convert.xml2js(response.data,{compact:true, spaces: 4});
this.setState({users:jsonObj});
//console.log(this.state.users.result.body.row[0].col[0]);
});
}
renderTableHeader = () => {
return <th>
<td>Division Full Path</td>
<td>Billable Hours</td>
<td>Vacation Hours Only</td>
<td>Total Hours</td>
</th>
}
renderTableRows = () => {
const {users} = this.state
console.log(users.result.body);
return <h1>Hello from table rows</h1>
}
render() {
//const { users } = this.state
return <table>
<thead>
<tr>
{this.renderTableHeader()}
</tr>
</thead>
<tbody>
<tr>
{this.renderTableRows()}
</tr>
</tbody>
</table>
}
"header": {
"col": [
{
"label": {
"_text": "Counter Source Date"
}
},
{
"label": {
"_text": "Employee Id"
}
},
{
"label": {
"_text": "Counter Hours"
}
},
{
"label": {
"_text": " Division Full Path"
}
},
{
"label": {
"_text": " Projects/Equip/Vessels\nBillable"
}
},
{
"label": {
"_text": "Counter Name"
}
}
]
}
"body": {
"row": [
{
"col": [
{
"_text": "01/01/2021"
},
{
"_text": "2183"
},
{
"_text": "8.00"
},
{
"_text": "01 - Fort Lauderdale/Salvage"
},
{
"_text": "No"
},
{
"_text": "Holiday"
}
]
}
]
}
Issue
The initial state doesn't match how it is accessed in renderTableRows.
this.state = {
users: []
}
Here this.state.users is an array, so this.state.users.result is undefined. This is fine until you then attempt to access a body property and the error TypeError: Cannot read property 'body' of undefined is thrown.
A Solution
You can either start with valid initial state:
this.state = {
users: {
result: {}
}
}
Or use a bunch of guard clauses in renderTableRows:
renderTableRows = () => {
const { users } = this.state
console.log(users.result && users.result.body);
return <h1>Hello from table rows</h1>
}
Or use Optional Chaining:
renderTableRows = () => {
const { users } = this.state
console.log(users.result?.body);
return <h1>Hello from table rows</h1>
}
Since you mention wanting to map through the rows the first option isn't what you want. If rendering rows it'll be something like:
renderTableRows = () => {
const {users} = this.state
return users.map(user => (....))
}
Update
I suggest setting your state to jsonObj.result properties, this is so you don't need to access the result property each render, it just shortens the access. Map this.state.users.headerColumns to the header columns and map this.state.rows to each row and additionally map the row columns.
class Table extends Component {
constructor(props) {
super(props);
this.state = {
users: {
headerColumns: [],
rows: [],
}
};
}
async componentDidMount() {
axios
.get(XMLData, {
"Content-Type": "application/xml; charset=utf-8"
})
.then((response) => {
var jsonObj = convert.xml2js(response.data, {
compact: true,
spaces: 4
});
this.setState({ users: {
headerColumns: jsonObj.header.col,
rows: jsonObj.body.row
} });
});
}
renderTableHeader = () => {
const { users: { headerColumns } } = this.state;
return (
<th>
{headerColumns.map(col => (
<td key={col.label._text}>{col.label._text}</td>
))}
<td>Total Hours</td>
</th>
);
};
renderTableRows = () => {
const { users: { rows } } = this.state;
return rows.map((row, index) => {
let computedTotal;
return (
<tr key={index}>
{row.col.map((value, index) => {
// compute time total from row data
return (
<td key={index}>{value}</td>
);
})}
<td>{computedTotal}</td>
</tr>
)});
};
render() {
return (
<table>
<thead>
<tr>{this.renderTableHeader()}</tr>
</thead>
<tbody>
{this.renderTableRows()}
</tbody>
</table>
);
}
}
I am using an API to fetch some data. When the page loads it fetches some random data, but I want to allow the user to sort the data by clicking a button. I have made a function to sort these data from the API I am using. What I want to do now is: When the button to sort data is clicked, I want the new data to be replaced with the old data.
Here is my current code:
class Data extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
offset: 0, perPage: 12 // ignore these two
};
}
// The random data that I want to be displayed on page load
receivedData() {
axios
.get(`https://corona.lmao.ninja/v2/jhucsse`)
.then(res => {
const data = res.data;
const slice = data.slice(this.state.offset, this.state.offset + this.state.perPage) // ignore this
const postData = slice.map(item =>
<tr key={Math.random()}>
<td>{item.province}, {item.country}</td>
<td>{item.stats.confirmed}</td>
<td>{item.stats.deaths}</td>
<td>{item.stats.recovered}</td>
</tr>
)
this.setState({
pageCount: Math.ceil(data.length / this.state.perPage), // ignore this
postData
})
});
}
// The data to be sorted when the "country" on the table head is clicked
sortData() {
axios
.get(`https://corona.lmao.ninja/v2/jhucsse`)
.then(res => {
const data = res.data;
var someArray = data;
function generateSortFn(prop, reverse) {
return function (a, b) {
if (a[prop] < b[prop])
return reverse ? 1 : -1;
if (a[prop] > b[prop])
return reverse ? -1 : 1;
return 0;
};
}
// someArray.sort(generateSortFn('province', true))
const tableHead = <tr>
<th onClick={() => someArray.sort(generateSortFn('province', true))}>Country</th>
<th>Confirmed Cases</th>
<th>Deaths</th>
<th>Recovered</th>
</tr>
this.setState({
tableHead
})
});
}
componentDidMount() {
this.receivedData()
this.sortData() // This function should be called on the "Country - table head" click
}
render() {
return (
<div>
<table>
<tbody>
{this.state.tableHead}
{this.state.postData}
</tbody>
</table>
</div>
)
}
}
export default Data;
Think a litte bit different. In the componentDidMount get you're Data in some form. Set it with setState only the raw Data not the html. Then resort the data on button click. React rerenders if the state changes automatically
class Data extends React.Component {
constructor(props) {
super(props);
this.state = {
data
}
}
getData() {
fetchData('url').then(res) {
this.setState({data: res.data})
}
}
componentDidMount() {
this.getData()
}
sort() {
let newSorted = this.state.data.sort //do the sorting here
this.setState({data: newSorted})
}
render() {
return() {
<table>
<tablehead><button onClick={this.sort.bind(this)}/></tablehead>
{this.state.data.map(data => {
return <tablecell>{data.name}</tablecell>
})}
</table>
}
}
}
TableHeader and TableBody component render twice after fetching data from API due to Table row are rendered twice and given duplicate key error in Reactjs.
Error : Each child in a list should have a unique "key" prop.
enter image description here
class Table extends React.Component {
state = {
headers: [],
accesors: [],
data: [],
loading: true
};
componentDidMount() {
instance.get('UserRole/GetDataList')
.then(response => {
var data = JSON.parse(response.data);
this.setState({
headers: Object.keys(data[0]),
data: data,
loading: false
}, () => this.setAccesors());
}, error => {
console.log(error);
});
}
render() {
const { headers, accesors, data } = this.state;
if (this.state.loading ) {
return "Loading...."
}
else {
return (
<table id="datatable-responsive" className="table table-
striped table-bordered">
<TableHeader headers={headers} />
<TableBody data={data} />
</table>
);
}
}
}
export default Table;
I'm attempting to add a new row of data to a table that occurs when a user inputs text into a field and clicks a button.
The button click is tied to a function (AddNewRow) which sends the data to a controller and then adds a new row to the table with the data.
The data is sent to the controller correctly and if the page is refreshed the new row is showing (because of the get request after mount) but the problem is the table doesn't update dynamically.
I keep getting a console error saying 'this is undefined' in the AddNewRow function.
Ive attempted to bind 'this' to the constructor by using both '.bind(this)' and AddNewRow() => {} but it still doesn't bind?
class App extends React.Component {
constructor() {
super();
this.state = {
tableData: [{
}],
};
}
componentDidMount() {
axios.get('/Jobs/GetJobs', {
responseType: 'json'
}).then(response => {
this.setState({ tableData: response });
});
}
AddNewRow(){
axios.post('/Controller/CreateJob', { Name: this.refs.NewJobName.value})
.then(function (response){
if(response.data.Error) {
window.alert(response);
}
else {
var data = this.setState.tableData;
this.setState.tableData.push(response);
this.setState({ tableData: data });
}
})}
render() {
const { tableData } = this.state;
return (
<div>
<button onClick={() => this.AddNewRow()} >ADD</button>
<input ref="NewJobName" type="text" placeholder="Name" />
<ReactTable
data={tableData}
/>
</div>
)
}
Use arrow function to make this available in the then function:
axios
.post('/Controller/CreateJob', { Name: this.refs.NewJobName.value })
.then((response) => {
if (response.data.Error) {
window.alert(response);
} else {
this.setState(prevState => ({
tableData: prevState.tableData.concat([response])
}));
}
});
From my latest React Search (filter) implementation which I have many people help me until it is success.
I have another issue that when the search (filter) cannot find any data in the content, it is stuck then I have to enter from the input box to initial search again.
The following attached image show that I search 111, if I delete it to 11 it is still working fine, but if I search 11111 it show nothing and after delete 11111 to 111 it still show nothing so I have to enter to start new search.
Issue image (after delete search value, nothing show)
The search (filter) code:
filterList = (e) => {
let { value } = e.target
this.setState({ value }, () => {
var searchValue = this.state.value.toLowerCase();
var updatedList = this.state.holder;
updatedList = updatedList.filter((item) => {
return Object.keys(item).some(key => item[key].toString().toLowerCase().search(searchValue) !== -1);
});
this.setState({ issues: updatedList });
});
}
After debugging from console I found that
this.state.holder (original temp is ok)
this.state.issues (filtered content is ok)
But this.state.pageOfItems (after it is empty, then nothing show)
Could anybody please help to check my code?
Note: I use the pagination from http://jasonwatmore.com/post/2017/03/14/react-pagination-example-with-logic-like-google
IssueList.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import 'whatwg-fetch';
import Pagination from '../components/Pagination';
import IssueAdd from '../components/IssueAdd';
class IssueList extends Component {
constructor(props) {
super(props);
this.state = {
issues: [],
holder: [],
pageOfItems: [],
};
this.createIssue = this.createIssue.bind(this);
this.onChangePage = this.onChangePage.bind(this);
this.filterList = this.filterList.bind(this);
}
componentDidMount() {
this.loadData();
}
//componentDidUpdate(prevProps) {
// this.loadData();
//}
// Load all new database after changed
loadData() {
fetch('/api/issues').then(response => {
if (response.ok) {
response.json().then(data => {
data.records.forEach(issue => {
issue.created = new Date(issue.created);
if (issue.completionDate) {
issue.completionDate = new Date(issue.completionDate);
}
});
this.setState({ issues: data.records, holder: data.records });
});
} else {
response.json().then(error => {
alert(`Failed to fetch issues ${error.message}`);
});
}
}).catch(err => {
alert(`Error in fetching data from server: ${err}`);
});
}
createIssue(newIssue) {
fetch('/api/issues', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newIssue),
}).then(response => {
if (response.ok) {
response.json().then(updatedIssue => {
updatedIssue.created = new Date(updatedIssue.created);
if (updatedIssue.completionDate) {
updatedIssue.completionDate = new Date(updatedIssue.completionDate);
}
const newIssues = this.state.issues.concat(updatedIssue);
this.setState({ issues: newIssues });
});
} else {
response.json().then(error => {
alert(`Failed to add issue: ${error.message}`);
});
}
}).catch(err => {
alert(`Error in sending data to server: ${err.message}`);
});
}
onChangePage(pageOfItems) {
this.setState({ pageOfItems: pageOfItems });
}
filterList = (e) => {
let { value } = e.target;
this.setState({ value }, () => {
var searchValue = this.state.value.toLowerCase();
var updatedList = this.state.holder;
updatedList = updatedList.filter((item) => {
return Object.keys(item).some(key => item[key].toString().toLowerCase().search(searchValue) !== -1);
});
this.setState({ issues: updatedList });
});
}
render() {
return (
<div>
<h1>Issue Tracker</h1>
<hr />
<div className="filter-list">
<form>
<fieldset className="form-group">
<input
type="text"
className="form-control form-control-lg"
placeholder="Search"
onChange={this.filterList}
/>
</fieldset>
</form>
</div>
<div className="panel panel-default">
<table className="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Status</th>
<th>Owner</th>
<th>Created</th>
<th>Effort</th>
<th>Completion Date</th>
<th>Title</th>
</tr>
</thead>
<tbody>
{this.state.pageOfItems.map(issue => (
<tr key={issue._id}>
<td>{issue._id}</td>
<td>{issue.status}</td>
<td>{issue.owner}</td>
<td>{issue.created.toDateString()}</td>
<td>{issue.effort}</td>
<td>{issue.completionDate ? issue.completionDate.toDateString() : ''}</td>
<td>{issue.title}</td>
</tr>
))}
</tbody>
</table>
</div>
<Pagination
items={this.state.issues}
onChangePage={this.onChangePage}
/>
<hr />
<IssueAdd createIssue={this.createIssue} />
</div>
);
}
}
export default IssueList;
Thanks #Justin Pearce and #Tomas Eglinskas.
Finally, I found that I have to comment out the condition in the pagination (setPage function).
setPage(page) {
var { items, pageSize } = this.props;
var pager = this.state.pager;
//if (page < 1 || page > pager.totalPages) {
// return;
//}
pager = this.getPager(items.length, page, pageSize);
var pageOfItems = items.slice(pager.startIndex, pager.endIndex + 1);
this.setState({ pager: pager });
this.props.onChangePage(pageOfItems);
}
I don't know that in the future, one day I will get the error???