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

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)}
/>

Related

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>
);
}
})}

Dynamically Creating Table Rows With React

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.

How do you remove a parent table row when child "delete" button is clicked in React JS?

I am rendering table data in React JS and am having trouble getting my table row to go hidden on click of a child "delete" button. My current handler and render functions looks like this:
...
changeHandler: function(e) {
...
},
deleteHandler: function(e) {
e.currentTarget.closest("tr").style.visibility = "hidden";
},
render: function() {
return (
<div>
<div class="container">
<select onChange={ this.changeHandler.bind(this) }>
<option></option>
...
</select>
<table>
<thead>
<tr>
...
</tr>
</thead>
<tbody>
{data.map(function(row, j) {
return <tr key={j}>
<td>{row.text}</td>
<td><a href="" onClick={this.deleteHandler.bind(this, j)}>delete</a></td>
</tr>
}
)}
</tbody>
</table>
</div>
</div>
);
}
...
When I click on the delete anchor, I get this error in the console:
Uncaught TypeError: Cannot read property 'bind' of undefined
I don't understand why my delete handler isn't being recognized and bound, when the changeHandler I'm using is. Could somebody please tell me how to get this event to hit the handler and how to target the parent tr to have it hidden?
After the correction in the typo above, I see that was not the error here. Check out the following fiddle to see it in action. There will be some need of style changes when you hide the row.
https://jsfiddle.net/vgo52rey/
The problem is with the binding of 'this' in the filter function in the fiddle. Abstract that out into another method so you can store the reference to this in a different variable, then you can keep the reference to this.delete or this.deleteHandler.
delete: function(e) {
e.currentTarget.closest("tr").style.visibility = "hidden";
},
renderRows: function() {
var shouldIRender =(row) => (this.state.filter === row.status || this.state.filter === "");
var self = this
return requests.filter(shouldIRender).map(function(row, j) {
return <tr key={j}>
<td style={tdStyle}>{row.title}</td>
<td style={tdStyle}>{row.status}</td>
<td style={tdStyle}>{row.created_at}</td>
<td style={tdStyle}>{row.updated_at}</td>
<td style={tdStyle}><a href="#" onClick={self.delete}>delete</a></td>
</tr>
}
)
},
in your render method now you can just supply this renderRows method return value:
<tbody>
{this.renderRows()}
</tbody>

Formatting data before render it

I am displaying some data in the view, but I need to formatted first, I was doing something like
val.toFixed(2) and that is OK, it works but the problem is that val sometimes comes with letters, and toFixed(2) is not taking that into account so is not displaying the letters.
So I need something that takes into account letters and numbers, the letters don't have to change, only the numbers which comes like 234235.345345435, and obviously I need it like this 234235.34.
Here is some of the code I am using
<table>
<tr>
<th ng-repeat='header in headers'>{{header.th}}</th>
</tr>
<tr>
<td ng-repeat='data in headers'>
<div ng-repeat='inner in data.td'>
<span ng-repeat='(prop, val) in inner'>{{val.toFixed(2)}}</span>
</div>
</td>
</tr>
</table>
and in the controller
$scope.LoadMyJson = function() {
for (var s in myJson){
$scope.data.push(s);
if ($scope.headers.length < 1)
for (var prop in myJson[s]){
prop.data = [];
$scope.headers.push({th:prop, td: []});
}
}
for (var s in $scope.data){
for (var prop in $scope.headers){
var header = $scope.headers[prop].th;
var data = myJson[$scope.data[s]][header];
$scope.headers[prop].td.push(data);
console.log($scope.headers[prop].td);
}
}
};
and I prepared this Fiddle
the way it is right now, is displaying the table properly, but as you see, the table is missing the name, it is because of the toFixed method.
So, what can I do ?
Create a custom filter to use on your template.
<table>
<tr>
<th ng-repeat='header in headers'>{{header.th}}</th>
</tr>
<tr>
<td ng-repeat='data in headers'>
<div ng-repeat='inner in data.td'>
<span ng-repeat='(prop, val) in inner'>{{val|formatValue}}</span>
</div>
</td>
</tr>
</table>
angular.module('whatever').filter('formatValue', function () {
return function (value) {
if (isNaN(parseFloat(value))) {
return value;
}
return parseFloat(value).toFixed(2);
}
});
You can try this :
That is a clean way to render formated data in view using angularjs as MVC
frontend framework :
Create a filter in your angular application.
Include your filter in your index.html.
use your filter like this : {{somedata | filterName}}
That is a simple angular filter to solve your problem, hope it will help you :
angular.module('app')
.filter('formatHeader', function() {
return function(data) {
if(angular.isNumber(data)) {
return data.toFixed(2);
}
return data;
}
});
And us it like this :
<table>
<tr>
<th ng-repeat='header in headers'>{{header.th}}</th>
</tr>
<tr>
<td ng-repeat='data in headers'>
<div ng-repeat='inner in data.td'>
<span ng-repeat='(prop, val) in inner'>{{val | formatHeader}}</span>
</div>
</td>
</tr>
You can take a look about these references :
angular functions
filter doc.
angular tutorials

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