Shallow rendering unexpected behaviour - reactjs

I have a Component called 'Grid' having a render method like this -
render(){
this.createTable();
return (
<div
style={{display:'flex',justifyContent:'center',flexDirection:'column'}}
>
<input
id='gridFilterText'
placeholder="filter text here....."
style={{width:'50%',height:'25px',borderRadius:'4px'}}
onChange={this.onType}
value={this.state.value}
></input>
<table style={{flex:'0.5',borderRadius:'4px',boxShadow:'-1px 3px 4px 0px black'}}>
<thead style={{textDecoration:'underline'}}>
<tr>
{this.headerArr}
</tr>
</thead>
<tbody>
{this.tableBody}
</tbody>
</table>
</div>
);
}
createTable() {
this.headerArr = this.props.headerList.map((item,index)=>(
<th key={index}>
{item}
</th>
));
this.tableBody = this.filteredProdList.map((item,index)=>(
<tr key={index} onMouseOver={this.onFocus} onMouseOut={this.onFocusRemove}>
{Object.keys(item).map((key,index)=>(
<td key={index}>{item[key]}</td>
))}
</tr>
))
}
I was trying to test this with this test (Jest and Enzyme) script -
test('Grid filters properly',()=>{
const gridWrapper=mount(<Grid headerList={['Id','Name','Stock']} prodList={items}/>);
const textBoxWrapper=gridWrapper.find('#gridFilterText');
textBoxWrapper.simulate('change',{target:{value:'p'}});
gridWrapper.update();
console.log(textBoxWrapper.debug());
expect(
gridWrapper
.find('table')
.children()
.find('tbody')
.children()
).toHaveLength(2);
})
What I am trying to acheive here is -
Render the Grid component
The grid component has a control in it (its not a child component)
Simulate an OnChange on the control by setting value (value ='p')
This should cause the grid to show filtered output (2 rows)
Why it works only if I use mount and it does not work if I use shallow.
As per my understanding shallow works on the component and does go down to the child components.
In this case, is part of the component and not its child component, right?
Please let me know if I can clarify further.

Related

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>

Can't Pass A Parameter for Datatable in Modal React JS

I have a datatable where it pops up a modal window if you select a row. I would like the modal window to display only a datatable of the selected row with additional details. However, passing in the selected row's ID for the modal window seems to not work and I don't know why? Or I'm not sure what is a better way to display information in a modal window?
**EDIT: I have discovered that if I replace this.state.TicketID with t and have t=0 then it also works so it must be something to do with calling this.state.TicketID within the x[]?
const {id, created} = t
})
return (
<div>
<Modal isOpen={this.state.modal} toggle={this.toggleModal} className={this.props.className}>
<ModalHeader toggle={this.toggleModal}>Selected Item </ModalHeader>
<ModalBody>
<Datatable>
<table className="table table-striped my-4 w-100">
<thead>
<tr>
<td>Ticket ID</td>
<td>Created On</td>
</tr>
</thead>
<tbody>
<tr key={this.state.TicketID}>
<td>{this.state.x[this.state.TicketID].id}</td>
<td>{this.state.x[this.state.TicketID].created}</td>
</tr>
</tbody>
</table>
</Datatable>
</ModalBody>
</Modal>
</div>
);
}
TicketID is set in an outer function and is kept in the state object. It is updating as expected. I noticed if I do console.log({this.state.x[0].id}) then it works. However, if I do console.log({this.state.x[this.state.TicketID].id}), it gives an Cannot read property 'id' of undefined. Any help or new suggestions would be appreciated!
actually for the current use case you don't even need a modal level state, so you just make the modal component as pure component(normal js function) and pass the values as props!
// pass these form the parent components
const {isOpend , onToggle, ticket:{id,created} ={}}= props // or from `currentActiveTicket` as described below
// if you mounting the modal element on the same datatable level
// and you only change the state of the parent component
// when a row is clicked you can add the `currentActiveTicket` as an object
// value of what you want to display on the modal(make sense)
})
return (
<div>
<Modal isOpen={isOpend} toggle={onToggle} className={this.props.className}>
<ModalHeader toggle={this.toggleModal}>Selected Item </ModalHeader>
<ModalBody>
<Datatable>
<table className="table table-striped my-4 w-100">
<thead>
<tr>
<td>Ticket ID</td>
<td>Created On</td>
</tr>
</thead>
<tbody>
<tr key={id}>
<td>{id}</td>
<td>{created}</td>
</tr>
</tbody>
</table>
</Datatable>
</ModalBody>
</Modal>
</div>
);
}
or you have to maintain the props changing and reflect that on the modal state
for Example I have fallen into this kind of issues because I wasn't understanding these concepts well enough - componentDidUpdate check the references on this

How to color specific cells in Table

I have a HTML Table in my ReactJS app and I want to color specific cells or rows there. I have my array in state and want to check differences between neighbor rows and then show this differencies by coloring them on red.
class MainTable extends Component {
constructor(props) {
super(props);
this.state = {
results: []
};
}
render() {
const sorted = _.sortBy(this.state.results, ['ORIGIN', 'DATASTAMP']);
return (
<div>
<div>
<Table hover striped bordered responsive size="sm">
<thead>
<tr>
<th>VERSION</th>
<th>DATE</th>
<th>ORIGIN</th>
</tr>
</thead>
<tbody>
{sorted.map(result =>
<tr>
<td>{result.VERSION}</td>
<td>{result.DATASTAMP}</td>
<td>{result.ORIGIN}</td>
</tr>
)}
</tbody>
</Table>
</div>
</div>
);
}
}
I have no idea how to do something like that. Maybe some idea? Sorry for noobie question, I'm new with ReactJS.
To mark some rows you can:
{sorted.map((result, index) =>
<tr className={`item-${index}`}>
<td>{result.VERSION}</td>
<td>{result.DATASTAMP}</td>
<td>{result.ORIGIN}</td>
</tr>
)}
Basically you first need some criteria on which to mark your element, than you can apply a class or style to it. Helpful is classnames so you could do something like that:
{sorted.map((result, index) =>
<tr className={classnames({
even: index % 2 == 0,
odd: !(index % 2 == 0)
})}>
That would add either even or odd to the classes of the <row>, depending on the index in the list.
I guess there are only two things to remember are:
element styles need to objects like: { backgroundColor: #000 } and
css classes need to be added as »className« property
You can use something like
<tr style={{'background-color': result.color}}>...</tr>
or
<tr style={{'background-color': shouldHighlight[key] ? 'red' : 'white'}}>...</tr>
Obviously in second case you need to findout before function, which table rows should be highlighted and store it in the array.
Also, you need to write your map function in format (result, key) => ... or you need to know id of the result.
You can add a flag in each element of your array to indicate if you need to set the background color of your cell or not.
And then use it like this in your render method :
render() {
const sorted = _.sortBy(this.state.results, ['ORIGIN', 'DATASTAMP']);
return (
<div>
<div>
<Table hover striped bordered responsive size="sm">
<thead>
<tr>
<th>VERSION</th>
<th>DATE</th>
<th>ORIGIN</th>
</tr>
</thead>
<tbody>
{sorted.map(result =>
<tr className={(result.colorFlag ? 'colored-background' : '')}>
<td>{result.VERSION}</td>
<td>{result.DATASTAMP}</td>
<td>{result.ORIGIN}</td>
</tr>
)}
</tbody>
</Table>
</div>
</div>
);
And of course, don't forget to create the CSS class
.colored-background {
background-color: #BADA55;
}

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

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 ?

How to correctly wrap few TD tags for JSXTransformer?

I have an array with items and I want to make something like this:
<tr>
(until have items in array
<td></td><td></td>)
</tr>
But if I do that, I get an JSXTransformer error :
Adjacent XJS elements must be wrapped in an enclosing tag
Working version:
{rows.map(function (rowElement){
return (<tr key={trKey++}>
<td className='info' key={td1stKey++}>{rowElement.row[0].value}</td><td key={td2ndKey++}>{rowElement.row[0].count}</td>
<td className='info' key={td1stKey++}>{rowElement.row[1].value}</td><td key={td2ndKey++}>{rowElement.row[1].count}</td>
<td className='info' key={td1stKey++}>{rowElement.row[2].value}</td><td key={td2ndKey++}>{rowElement.row[2].count}</td>
<td className='info' key={td1stKey++}>{rowElement.row[3].value}</td><td key={td2ndKey++}>{rowElement.row[3].count}</td>
<td className='info' key={td1stKey++}>{rowElement.row[4].value}</td><td key={td2ndKey++}>{rowElement.row[4].count}</td>
.......
</tr>);
})}
I tried this one. But with <div> enclosing tag it doesn't work fine.
Answer here:
Uncaught Error: Invariant Violation: findComponentRoot(..., ...$110): Unable to find element. This probably means the DOM was unexpectedly mutated
<tbody>
{rows.map(function (rowElement){
return (<tr key={trKey++}>
{rowElement.row.map(function(ball){
console.log('trKey:'+trKey+' td1stKey'+td1stKey+' ball.value:'+ball.value+' td2ndKey:'+td2ndKey+' ball.count:'+ball.count);
return(<div key={divKey++}>
<td className='info' key={td1stKey++}>{ball.value}</td><td key={td2ndKey++}>{ball.count}</td>
</div>);
})}
</tr>);
})}
</tbody>
Please, advise me how to properly wrap few TD tags!
I tried use a guide Dynamic Children, but JSXTransformer doesn't allow me do that.
The following error usually occurs when you are returning multiple elements without a wrapping element
Adjacent XJS elements must be wrapped in an enclosing tag
Like
return (
<li></li>
<li></li>
);
This doesn't work because you are effectively returning two results, you need to only ever be returning one DOM node (with or without children) like
return (
<ul>
<li></li>
<li></li>
</ul>
);
// or
return (<ul>
{items.map(function (item) {
return [<li></li>, <li></li>];
})}
</ul>);
For me to properly answer your question could you please provide a JSFiddle? I tried to guess what you're trying to do and heres a JSFiddle of it working.
When using the div as a wrapper its actually never rendered to the DOM (not sure why).
<tr data-reactid=".0.0.$1">
<td class="info" data-reactid=".0.0.$1.$0.0">1</td>
<td data-reactid=".0.0.$1.$0.1">2</td>
<td class="info" data-reactid=".0.0.$1.$1.0">1</td>
<td data-reactid=".0.0.$1.$1.1">2</td>
<td class="info" data-reactid=".0.0.$1.$2.0">1</td>
<td data-reactid=".0.0.$1.$2.1">2</td>
<td class="info" data-reactid=".0.0.$1.$3.0">1</td>
<td data-reactid=".0.0.$1.$3.1">2</td>
</tr>
EDIT: React 16+
Since React 16 you can now use fragments.
You would do it like this now
return <>
<li></li>
<li></li>
<>;
Or you can use <React.Fragment>, <> is shorthand for a HTML fragment, which basically is just a temporary parent element that acts as a container, once its appended to the document it no longer exists.
https://reactjs.org/docs/fragments.html
https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
So you have pairs of <td> elements which you want to return from a .map. The easiest way to do this is to just wrap them in an array.
<tr>
{data.map(function(x, i){
return [
<td>{x[0]}</td>,
<td>{x[1]}</td>
];
})}
</tr>
Don't forget the comma after the first </td>.
With the release of React 16, there is a new component called Fragment.
If are would like to return a collection of elements/components without having to wrap them in an enclosing element, you can use a Fragment like so:
import { Component, Fragment } from 'react';
class Foo extends Component {
render() {
return (
<Fragment>
<div>Hello</div
<div>Stack</div>
<div>Overflow</div>
</Fragment>
);
}
}
Here is how you will do it
<tbody>
{this.props.rows.map((row, i) =>
<tr key={i}>
{row.map((col, j) =>
<td key={j}>{col}</td>
)}
</tr>
)}
</tbody>

Resources