React.js component apparently renders, but no change in what's displayed - reactjs

This is my first venture into React territory, so I'm sure my problem is a simple newbie one. As an exercise I'm trying to convert an existing working web application to use React. I have a ProductTable which displays a list of products. It has a 'selectedProducts' field as part of its state, which gets updated whenever the product selection changes. My assumption was that changing this value (state) should cause a re-rendering with any new selection.
On first display, the component displays successfully and correctly shows the list of products. However, when a change is made to the selection, what is displayed on screen does not change. Yet from console logging and use of React tools in Chrome, I can see that the 'selectedProducts' field state has indeed changed, and in fact a console log entry in the render method shows that render for the component is in fact being called, with the new selection. But it's not changing what's displayed on screen - the table continues to show the old selection of products - and I really can't figure that out.
Any suggestions as to where to look? I appreciate it might be clearer with some code here but it's slightly difficult to present it in a concise way given that it's a conversion of an existing web application, so I hope my description above is sufficient to suggest something to the experts here.
EDIT: Code of component added here. As you will see, notification is done by means of pub/sub. I've stuck with the global 'productSelection' object for now, but that would be refactored in due course. At the moment the idea is that some other code makes changes to that global variable and then notifies the ProductTable component via pub/sub. As I mentioned, the notification is working fine, the 'selectedProducts' variable is clearly being changed and render is being run - but no visible change is occurring.
var ProductTable = React.createClass({
getInitialState: function () {
return {
selectedProducts: [],
};
},
componentWillMount: function(){
this.pubsubToken = Arbiter.subscribe('products/selection', function() {
// update my selection when there is a message
console.log("Setting selection to "+productSelection.length);
this.setState({ selectedProducts: productSelection });
}.bind(this));
},
componentWillUnmount:function(){
Arbiter.unsubscribe(this.pubsubToken);
},
render: function () {
var rows = this.state.selectedProducts.map(function (product, i) {
return <ProductRow key={product.code} product={product}/>;
});
console.log("Running render for ProductTable with "+rows.length+" rows");
return (
<div>
<label htmlFor="products" id="productsLabel">Available products (click or double-click to select):</label>
<table id="products" className="st-container">
<thead>
<tr>
<td className="st-head" style={{padding:"0px 18px 0px 0px"}}>
<table cellSpacing="0" cellPadding="0" border="0" className="st-head-table" style={{width: "100%"}}>
<thead>
<tr>
<th className="rem" colSpan="0"></th>
<th className="code" colSpan="0">Code</th>
<th className="name" colSpan="0">Name</th>
<th className="pack" colSpan="0">Pack</th>
<th className="size" colSpan="0">Size</th>
<th className="attribs" colSpan="0"></th>
<th className="price" colSpan="0">Price</th>
<th className="unit" colSpan="0">Unit</th>
<th className="rrp" colSpan="0">RRP</th>
<th className="vat" colSpan="0">VAT</th>
</tr>
</thead>
</table>
</td>
</tr>
</thead>
<tbody>
<tr>
<td className="st-body" style={{padding:"0px"}}>
<div className="st-body-scroll" style={{overflowY: "scroll", maxHeight: "250px"}}>
<table cellSpacing="0" cellPadding="0" border="0" className="st-body-table" style={{width: "100%"}}>
<tbody id="productRows">
${rows}
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
</div>
);
}
});

Ok I see, you need to use componentDidMount instead of componentWillMount because, as stated in the doc : If you call setState within this method, render() will see the updated state and will be executed only once despite the state change.
https://facebook.github.io/react/docs/component-specs.html#mounting-componentwillmount
Use componentDidMount and everything should be fine.
Edit:
you don't need ${rows} but just {rows}. Also I would suggest you check your new data, are they really different form the initial one ?

Related

Table rendering issue from API responce in ReactJs

I am new to react and facing some issues with a table display.
When I am trying to render data using State entire string value gets rendered, instead of an actual table.
A small snippet of data displayed in the browser:
But when I copy-paste the data from the screen to the render() method, the table gets displayed properly.
The current render method-
render() {
this.setValueForState();
return (
<div>
<table>
<thead>
<tr>
{this.state.Header}
</tr>
</thead>
<tbody>
{this.state.Detail}
</tbody>
</table>
</div>
);
}
I get the following error in the console. I have made sure that there are no BLANK spaces in the data.
index.js:1 Warning: validateDOMNesting(...): Text nodes cannot appear as a child of <tr>.
Warning: validateDOMNesting(...): Text nodes cannot appear as a child of <tbody>.
I looked up this error online for the above error and removed all Blank spaces from the result string.
Not sure what am I doing wrong.
this.state.Header and this.state.Detail are strings, which are getting rendered as text nodes.
As the error message states, you can not put text nodes inside <tr> or <tbody> tags as you have done.
HTML tables need the text nodes to be inside <td> tags.
See below for valid table markup.
<div>
<table>
<thead>
<tr>
<td>{this.state.Header}</td>
</tr>
</thead>
<tbody>
<tr>
<td>{this.state.Detail}</td>
</tr>
</tbody>
</table>
</div>
Please remove some whitespace. you can try with below
<tr key={id}>{this.state.Header}</tr>;

In react bootstrap how do I show the contents of an array in an html table

I have an array that I add to, each time a button is pressed. That part appears to work. When I output it to a console, I can see that each one is getting added.
const addToPool = () => {
diePool[poolCount] = (
<die
type={diceType}
number={diceNumber}
adjuster={diceAdjuster}
/>
);
poolCount++;
};
How do I loop through and display that in my html table after my form?
return (
<div className="App">
<header className="App-header">
<Form>
<Button onClick={addToPool} aria-controls="diceRoll" aria-expanded={open} variant="secondary" size="sm">
Add to Pool
</Button>
</ButtonToolbar>
</Form>
<Fade in={open}>
<div id="diceRoll">
The result is: {randNum}
</div>
</Fade>
</header>
<Table responsive>
<caption>Dice Pool</caption>
<thead>
<tr>
<th>Type</th>
<th>Number</th>
<th>Adjuster</th>
</tr>
</thead>
<tbody>
</tbody>
</Table>
</div>
);
}
export default App;
In order for React to update the user interface to reflect changes, it expects you to notify it that a change has been made. You do this my modifying the state of your component. In your addToPool method, you are modifying a local variable called diePool. That's just a member variable of the control object, not the state, and React won't know you've made a change.
Instead, modify the state with something like this:
const addToPool(diceType, diceNumber, diceAdjuster) {
this.setState(prevstate => {
return {
diePool: prevstate.diePool.push(
{ type: diceType, number: diceNumber, adjuster: diceAdjuster }
)
}
});
}
For this to work, your component will have to have an initial state to work with. You create this in the constructor:
constructor(props) {
this.state = { diePool: [] };
}
Now, whenever your code calls addToPool, the state will be updated and through setState, React will know about it and call the render method as needed.
In your render method, you will need to consult the state to place the dice rolls in your table. You can do so by using map over your array of dice rolls:
<Table responsive>
// ...snip...
<tbody>
{this.state.diePool.map((roll, index) => {
<tr key={index}>
<td>{roll.type}</td>
<td>{roll.number}</td>
<td>{roll.adjuster}</td>
</tr>
})}
</tbody>
</Table>
(Please note the use of index here to make sure that each table row has a unique index - this is a requirement for React to be able to update table rows individually when it can.)
What's important here is that the component state is a crucial concept in React. If you modify something and you expect React to react (ha!) to it, you'll have to modify the state through setState (not by modifying this.state directly). That's the only way React will know about it. React will then work its magic and call the render methods of your component and its children, as necessary.
inside your body tag, you can iterate through an array using the map method (https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/map)
<tbody>
{dice.map((item,index) => {
return(
<tr key={index}>
<td>{item.key1}</td>
etc...
</tr>
)})}
</tbody>
I figured it out and posting it here in case others have trouble:
declared a counter:
const [poolCount, setPoolCount] = useState(0);
Created my addToPool:
const addToPool = () => {
setDiePool([
...diePool,
{
dp1 : poolCount,
dp2 : diceType,
dp3 : diceNumber,
dp4 : diceAdjuster
}
]);
setPoolCount(poolCount+1);
};
Call my addToPool when I click the button:
<Button onClick={addToPool} aria-controls="diceRoll" aria-expanded={open} variant="secondary" size="sm">
Add to Pool
</Button>
Then create my table and loop:
<Table responsive>
<caption>Dice Pool</caption>
<thead>
<tr>
<th>#</th>
<th>Type</th>
<th>Number</th>
<th>Adjuster</th>
</tr>
</thead>
<tbody>
{diePool.map(die => (<tr key={die.dp1}><td>{die.dp1}</td><td>{die.dp2}</td><td>{die.dp3}</td><td>{die.dp4}</td></tr>))}
</tbody>
</Table>

Getting undefined when using target '_blank' with ui-router

Hi I'm having the below issue.
Below is my html page
<div class="content" ng-controller="DataController">
<table class="table table-striped" id="show" ng-if="datalist.length>0">
<thead>
<th>State</th>
<th>District</th>
<th>Non-DND's</th>
</thead>
<tbody>
<tr dir-paginate="data in datalist| filter:search|orderBy:sortKey:reverse|itemsPerPage:10" style="cursor:pointer;">
<td>{{data.state}}</td>
<td>{{data.district}}</td>
<td><a ng-click="getnre(data.nondnd,data.dnd,data.land,data.email)" ui-sref="numsemailsdata" target="_blank">{{data.nondnd.length}}</a></td>
</tr>
</tbody>
</table>
<dir-pagination-controls max-size="10" direction-links="true" boundary-links="true" style="float:right" ng-if="datalist.length>0">
</dir-pagination-controls>
</div>
Below is the code in my controller.js
app.controller("DataController", function($scope, DataService) {
$scope.datalist=DataService.getData();
$scope.getnre=function(ndnd,dnd,land,email) {
$scope.numsem = {
ndnds : ndnd,
dnds : dnd,
lands : land,
emails : email
}
}
});
Below is the numsdetails.html
<div class="content" ng-controller="DataController">
<table class="table table-striped">
<thead>
<th>Non-DND's</th>
</thead>
<tbody>
<tr ng-repeat="ndnd in numsems.ndnds">
<td>{{ndnd}}</td>
</tr>
</tbody>
</table>
</div>
Here I'm displaying the non-dnd's count in my first html page and I need to display the non-dnd's in new tab which is numsemails.html
When I'm trying to bind the data to numsemails.html, I'm getting the data as undefined even I'm binding the data from same controller.
Please help me with a solution.
The reason you are getting undefined for numsems is because when you open a new page (blank) you create an entire new instance of your app. It is like loading the page for the first time. Think of it as a totally different browser instance, because that is what it is. You can pass a parameter using stateparams, this gets passed in the url, however you are trying to pass an entire object so it becomes a bit more difficult.
There are multiple solutions to your problem. You can pass some data in the url, or you can use localstorage, $window, or cookies to store the data. I'm sure there are also other solutions. Choose one of these methods to hand your data properly and we can help you with it.
This issue has been discussed in other threads.
ui-router: open state in new tab with target=“_blank” with object params
Your controller code is wrong, you're missing brackets and braces:
app.controller("DataController", function($scope) {
$scope.datalist = DataService.getData();
$scope.getnre = function(ndnd,dnd,land,email) {
$scope.numsem = {
ndnds : ndnd,
dnds : dnd,
lands : land,
emails : email
};
}
});

React.js, handling onMouseOver event

being a noob in React I'm facing a problem: the component returns a simple nested table, so each cell of the 'big-table-id' contains another small table 'small-table-id'.
Thing is that every time the mouseover event occurs I'm always getting the 'small-cell-*' as target.id, even if the event handler is referenced in the parent (big) table. Is there any way to get the parent table kinda 'non-transparent' so that I could receive 'big-table-cell-1' or 'small-table-id'?
(using Rails with 'react-rails' gem)
var Tables = React.createClass({
handleMouseOver: function(e){
console.log(e.target.id)
},
render: function(){
return (
<table id='big-table-id' onMouseOver={this.handleMouseOver}>
<tr>
<td id='big-table-cell-1'>
<table id='small-table-id'>
<tr>
<td id='small-cell-1>
text 1
</td>
<td id='small-cell-2'>
text 2
</td>
</tr>
</table>
</td>
</tr>
</table>
)
}
});
The DOM lets you select an elements child and parent nodes with methods like firstChild and parentElement. You should look into those.
Edit: also not sure if this would work but you could try wrapping the big table in a div and setting the callback there and seeing what it references.

How to get Angular to update the Ui from within the controller

I have the following html.
<div ng-controller="CustCtrl">
<table class="table table-condensed">
<thead>
etc.
</thead>
<tbody>
<tr ng-repeat="customer in customers" data-cust-id="{{customer.Id}}">
<td>
<button ng-model="Id" tracking-{{customer.Tracking}} ng-click="startTrackingCustById(customer.Id)">
</button>
</td>
etc.
</tr>
</tbody>
</table>
So the button has a class that is databound to the customer.Tracking value which is either true or false. When the button is clicked the startTrackingCustById() method is successfully called and the customer object in the customers object is successfully changed like customer.Tracking = true.
But the buttons class is not updated. What am I missing?
Look at using ng-class . For a boolean value in scope you would use:
ng-class="{'classNameToAddIfTrue':customer.Tracking}"
In the CustCtrl I wrapped the call that updated the customers array like this
$scope.$apply($scope.customers[i].Tracking = true);
Based on the suggestion in an answer I will link to when I find it that basically said "If you are having trouble updating the view you most likely need to use $scope.$apply
So that get's it to work. Now I need to figure out why and how.

Resources