I've inherited a large pile of code and it generally works. This is running on v0.13.3.
The problem I am having is in sorting columns. Whenever I invoke the handler for the click event below, I am getting a null this.state. I must be missing something simple because I have done a lot of stare/compare vs other working code and I just don't see it but this example component is hassling me.
I've simplified down to just the problem component as a standalone html file with static sample data:
<div id="content">Loading ...</div>
<script>
var theData = [{"id":"47483648","labName":"Lab0"},{"id":"47483650","labName":"Lab1"},{"id":"47483651","labName":"Lab2"},{"id":"47483654","labName":"Lab3"}];
function render() {
React.render(React.createElement(aTable, {data: theData}),document.getElementById("content"));
}
var aTable = React.createClass({displayName: "aTable",
handleHeaderClick: function(sortBy) {
console.log("firing aTable handleHeaderClick");
//this.state is null here
var newState = this.state;
if (this.state.sortBy === sortBy && this.state.sortOrder === 'asc') {
newState.sortOrder = 'desc';
}
else {
newState.sortOrder ='asc';
}
newState.sortBy = sortBy;
this.setState(newState);
},
render: function(){
var theItems = $.map(this.props.data, function (key, value) {
console.log("the items", key);
return (
React.createElement("tr",{key: key.id + "-row"},
React.createElement("td", {key: key.agentid}, key.id),
React.createElement("td", {key: key.labName}, key.labName),
)
);
});
return (
React.createElement("div", {id: "holder"},
React.createElement("div", {id: "a-table"},
React.createElement("table",null,
React.createElement("thead",null,
React.createElement("tr",null,
React.createElement("th",null," The ID"),
React.createElement("th",
{onClick: this.handleHeaderClick.bind(this, "labName")},
"Lab Name")
)
),
React.createElement("tbody",null,
theItems
)
)
)
)
);
}
});
$(document).ready(function () {
render();
});
This throws up an exception on the null state:
basic.html:37 Uncaught TypeError: Cannot read property 'sortBy' of null
Which refers to this line:
if (this.state.sortBy === sortBy && this.state.sortOrder === 'asc') {
I have latitude to change the handler logic, but am stuck with this pattern for the time being.
Thanks in advance.
EDIT 1
This is a working fiddle where I worked out the sorting by doing a field sort on the data array after Sy pointed out where I was missing the initial state: working fiddle
state is null because you didn't set an initial value.
Test = React.createClass({
getInitialState: function() {
return {};
}
})
this is equivalent to
class Test extends Component {
constructor(props) {
this.state = {}
}
}
Related
I have a ecommerce website, which has list of products similar to amazon. i am trying to look for a element and click it. i have tried by filter and map and both not working for me. Please help.
Below is the code for each product:
<b class="productNameHover pcursor word-break">product1</b>
<b class="productNameHover pcursor word-break">product2</b>
<b class="productNameHover pcursor word-break">product3</b>
<b class="productNameHover pcursor word-break">product4</b>
say i have 4 elements in my page. so, i have tried to get the count and it working for me.
var products = element.all(by.className('productNameHover')) ;
expect(products.count()).toBe(4);
when i tried filter,
Solution A) not working, no error message but did nothing
var products = element.all(by.className('productNameHover'));
products.filter(function(elem) {
return products.getText().then(function(text) {
return text === 'product4';
});
}).click();
browser.sleep(5000);
Solution B) not working; index out of bound; Trying to access element at index: 0, but there are only 0 elements that match locator
var products = element.all(by.className('productNameHover'));
products.filter(function(elem) {
return products.getText().then(function(text) {
return text === 'product4';
});
}).first().click();
browser.sleep(5000);
Soluction C) no error message but did nothing
var products = element.all(by.className('productNameHover'));
products.filter(function(elem) {
return products
.element(by.xpath(
"//div[#class='item text-center slide-image-content active']/img"
))
.getText()
.then(function(text) {
expect(text).toContain(product4);
})
.then(function(filteredElements) {
filteredElements.first().click();
});
});
browser.sleep(5000);
Solution D) This is working and giving me all the products; but i need to either click on a single product or loop through
var products = element.all(by.className('productNameHover'));
products.map(function(item) {
return item.getText();
}).then(function(txt) {
console.log(txt);
//expect(txt).toContain("product 4")
});
Solution E) not working and no error message
products.map(function(item) {
return item.getText();
}).then(function(txt) {
console.log(txt);
if( txt== 'product4') {
console.log(item);
item.click();
browser.sleep(5000);
}
});
Solution F) I tried to click on all the elements in loop but it is clicking on the first element and not clicking on the second one; it is giving Failed: stale element reference: element is not attached to the page document.
products.map(function(item) {
browser.sleep(5000);
item.click();
browser.sleep(5000);
var backButton = element.all(by.className('btn btn-outline btn-big mt-3 ml-0 ml-sm-2')).first() ;
backButton.click();
browser.sleep(5000);
})
You have wrong understand the Protractor API: each() / map() / filter(). Recommend you to learn JavaScript Array's forEach() / map() / filter() to have a better understanding before you use Protractor API.
Solution A)
return products.getText() should be return elem.getText().
filter() return an element array, thus you can't call click() on array.
Solution B)
return products.getText() should be return elem.getText().
Solution E) map() return array too.
products.map(function(item) {
return item.getText();
}).then(function(txt) {
// txt = [ 'product1', 'product2', 'product3', 'product4']
console.log(txt);
if( txt == 'product4') { // this condition never be true
console.log(item);
item.click();
browser.sleep(5000);
}
});
// correct code example
products.map(function(item) {
return item.getText();
}).then(function(txts) {
// txts = [ 'product1', 'product2', 'product3', 'product4']
console.log(txts);
let index = txts.indexOf('product4');
if( index > -1) {
products.get(index).click();
browser.sleep(5000);
}
});
Have you tried using each() function of the protractor API ?
var products = element.all(by.className('productNameHover'));
products.each(function(element) {
return element.getText().then(function (text) {
if(text === 'product4') {
return element.click();
}
});
})
I would suggest you to switch to new async/await syntax of writing js code.
const products = element.all(by.className('productNameHover'));
products.each(async function(element) {
let text = await element.getText();
if(text === 'product4') {
await element.click();
}
});
You can also use map() function -
element.all(by.className('productNameHover')).map(function(element) {
return element.getText(function(text) {
return text === 'product4'
});
}).then(function(elements) {
for (var i = 0; i < elements.length; i++) {
elements.get(i).click();
}
});
Sounds like you got it working and I may be misunderstanding, but if you're just trying to click on a specific product in the list, you can use
element(by.cssContainingText('.productNameHover', 'product4')).click();
I see that the ag-grid-react repo has types, and I also see that the ag-grid-react-example repo has examples. But how do I put the two together and create a cell editor with React and Types?
I'm guessing it's something like this but I can't make TypeScript happy:
class MyCellEditor implements ICellEditorReactComp {
public getValue() {
// return something
}
public render() {
const { value } = this.props
// return something rendering value
}
}
I implemented ICellEditor and used ICellEditorParams for prop definitions. For example, this MyCellEditor example from their documentation:
// function to act as a class
function MyCellEditor () {}
// gets called once before the renderer is used
MyCellEditor.prototype.init = function(params) {
// create the cell
this.eInput = document.createElement('input');
this.eInput.value = params.value;
};
// gets called once when grid ready to insert the element
MyCellEditor.prototype.getGui = function() {
return this.eInput;
};
// focus and select can be done after the gui is attached
MyCellEditor.prototype.afterGuiAttached = function() {
this.eInput.focus();
this.eInput.select();
};
// returns the new value after editing
MyCellEditor.prototype.getValue = function() {
return this.eInput.value;
};
// any cleanup we need to be done here
MyCellEditor.prototype.destroy = function() {
// but this example is simple, no cleanup, we could
// even leave this method out as it's optional
};
// if true, then this editor will appear in a popup
MyCellEditor.prototype.isPopup = function() {
// and we could leave this method out also, false is the default
return false;
};
became this:
class MyCellEditor extends Component<ICellEditorParams,MyCellEditorState> implements ICellEditor {
constructor(props: ICellEditorParams) {
super(props);
this.state = {
value: this.props.eGridCell.innerText
};
}
// returns the new value after editing
getValue() {
// Ag-Grid will display this array as a string, with elements separated by commas, by default
return this.state.value;
};
// Not sure how to do afterGuiAttached()
// if true, then this editor will appear in a popup
isPopup() {
return true;
};
render() {
return (
<div>
hello
</div>
);
}
}
I have the following markup in a form mixed with some asp.net razor:
<div class="account-form__field-container" ng-show="postcodeRequired()" ng-cloak>
#Html.LabelFor(x => x.Postcode)
#Html.TextBoxFor(x => x.Postcode, new { #class = "account-form__field", placeholder = "Postcode here...", ng_required = "postcodeRequired()",ng_validpostcode="", ng_model = "postcode", ng_init = "postcode = '" + Model.Postcode + "'" })
#Html.ValidationMessageFor(x => x.Postcode, null, new { #class = "account-form__error-message" })
<span class="account-form__error-message" ng-show="registrationForm.$submitted && registrationForm.Postcode.$error.required" ng-cloak>
Please enter your postcode
</span>
<span class="account-form__error-message" ng-show="registrationForm.$submitted && !validPostCode()" ng-cloak>
Please enter valid postcode
</span>
</div>
I have a dropdown which will show hide the postcode field, so if uk selected the postcode field will show. The field is required but additionally I am doing a check in whether is a valid postcode via a webservice. The angular controller that deals with form submission looks like:
$scope.submitForm = function () {
$scope.registrationForm.$submitted = true;
if ($scope.enableSubmit()) {
registrationForm.submit();
}
};
$scope.postcodeRequired = function () {
return $scope.country === 'United Kingdom';
};
$scope.validPostCode = function () {
if ($scope.postcodeRequired()) {
if ($scope.postcode !== undefined && $scope.postcode.length > 5) {
postcodeService.ValidatePostCode($scope.postcode).success(function (response) {
return response;
});
} else {
return false;
}
}
return true;
};
$scope.enableSubmit = function () {
return $scope.registrationForm.$valid
&& $scope.passwordsMatch()
&& $scope.acceptTerms
&& $scope.validPostCode();
};
The postCodeService is just doing an http get to validate the post code that returns true or false. The issue i have is on submitting it validates the postcode but then goes into a loop and gives the following error:
angular.min.js:34 Uncaught Error: [$rootScope:infdig] http://errors.angularjs.org/1.4.0/$rootScope/infdig?p0=10&p1=%5B%5D
at angular.min.js:34
at m.$digest (angular.min.js:563)
at m.$apply (angular.min.js:571)
at l (angular.min.js:373)
at O (angular.min.js:388)
at XMLHttpRequest.N.onload (angular.min.js:392)
I have seen other people with this issue when doing an ng-repeat but as you can see I am not doing that.
Any ideas?
Without a plunkr to test against and verify its hard to tell exactly what is causing the infinite digest cycle loop. However I believe it might be cause by the amount of calls made towards your $scope.validPostCode function (which wasn't correctly returning its validity). Basically the change proposed is to only call the validate function when the postcode has been changed (trigged by ng-change on the field). The result of that function sets $scope.validPostCode variable to true or false, which is then what is checked for validity;
HTML (add ng-change to the input)
#Html.TextBoxFor(x => x.Postcode, new { <!-- other attributes -->, ng_change = "validatePostCode()" })
JavaScript
$scope.postcodeRequired = function () {
return $scope.country === 'United Kingdom';
};
// by default its not valid
$scope.validPostCode = false;
// our Validition check
$scope.validatePostCode = function () {
if ($scope.postcodeRequired()) {
if ($scope.postcode !== undefined && $scope.postcode.length > 5) {
postcodeService.ValidatePostCode($scope.postcode).success(function (response) {
$scope.validPostCode = response;
});
} else {
$scope.validPostCode = false;
}
} else {
$scope.validPostCode = true;
}
};
// call our function to properly set the initial validity state.
$scope.validatePostCode();
$scope.enableSubmit = function () {
return $scope.registrationForm.$valid
&& $scope.passwordsMatch()
&& $scope.acceptTerms
&& $scope.validPostCode;
};
I'm trying to call a few methods dynamically in my React component.
so I have this code where I want to call a function stepOne, stepTwo, etc. whenever that step is achieved, but this needs to be dynamically called to add new steps in the future.
However I tried already a couple of methods (hasOwnProperty,typeof this[methodName], this.{methodName}()) and can't get to call the right method.
Here is my code:
class MyComponent extends React.Component<Props,State>{
steps = [
'stepOne',
'stepTwo',
'stepThree',
];
state = {step:1};
stepOne(){
return 'This is Step One';
}
stepTwo(){
return 'This is Step Two';
}
_getContent(){
let content = 'Step not exists';
const methodName = this.steps[this.state.step - 1];
if (typeof this[methodName] === 'function') {
content = this[methodName]();
}
return content;
}
render(){
return '<div>' + this._getContent() + '</div>'
}
}
In this example, I always get undefined in the typeof this[methodName] operation
Try creating the map of functions and bind this context to your created functions
class MyComponent extends React.Component<Props,State>{
constructor(props){
super(props);
this.stepOne = this.stepOne.bind(this);
this.stepTwo = this.stepTwo.bind(this);
this.funcMap = {
'1': this.stepOne,
'2': this.stepTwo
};
this.state = {step: '1'};
}
stepOne(){
return 'This is Step One';
}
stepTwo(){
return 'This is Step Two';
}
_getContent(){
let content = 'Step not exists';
const method = this.funcMap[this.state.step];
if (typeof method === 'function') {
content = method();
}
return content;
}
render(){
return '<div>' + this._getContent() + '</div>'
}
}
i am having a problem where i cannot able to increment the ID of the state element for every addition of elements in the row. What i am getting is same number is repeating for every addition of elements, i need something like ID should be 1 to n numbers of addition.
In text-box, i am not entering the ID, i enter only the LText(Name).
In general, the ID should generate for every addition of elements.
What i have tried is..
export default class EPIM091 extends cntrl.WITBase {
constructor(props) {
super(props);
const witState = this.state;
if (Object.keys(witState).length == 0) {
witState.model = { LID: 1, LText: '', SOrder: '', Inventoried: false, Location:[] }; //Here i need to store all other state values to the Location Array
}
}
clickAction = (e) => {
if (e.id == 'btn_add') {
//var i = 1;
const { model } = this.state;
if (model.LText != null) {
if ((model.Location || []).length == 0) {
model.Location = [];
}
// this.setState(prevState => { return { LID: e.id == 'btn_add' ? prevState.LID + 1 : prevState.LID - 1 } }); //This does not worked
model.Location.push({ "LID": model.LID.toString(), "LText": model.LText, "SOrder": model.SOrder, "Inventoried": model.Inventoried.toString() });
this.setState({
model: model,
LID: model.LID + 1 //This also not worked
});
}
}
};
render(){
// Due to confusion of code, i did not add the textboxes codes
<cntrl.WITButton id="btn_add" onWitClick={this.clickAction} />
}
I need something like when i add, i should get the Unique LID from 1 to the number of elements added. What i am getting is same ID i.e 1. Thank you
Many problems here :
1) You are trying to modify a variable that you declared constant:
const witState = this.state;
if (Object.keys(witState).length == 0) {
witState.model = { LID: 1, LText: '', SOrder: '', Inventoried: false, Location:[] };
2) You are trying to access state variable, but you never define it:
const { model } = this.state;
3) You try to modify it even if it's declared constant:
if (model.LText != null) {
if ((model.Location || []).length == 0) {
model.Location = [];
}
// this.setState(prevState => { return { LID: e.id == 'btn_add' ? prevState.LID + 1 : prevState.LID - 1 } }); //This does not worked
model.Location.push({ "LID": model.LID.toString(), "LText": model.LText, "SOrder": model.SOrder, "Inventoried": model.Inventoried.toString() });
this.setState({
model: model,
LID: model.LID + 1 //This also not worked
});
}
Unless there is a lot of code you didn't show us, you must have a lot of errors in your console. Watch it.
-[Edit]-:
The problem is that I can't really help you unless I have the whole code, because as it is, it doesn't make sense. If you just want to increment your LID state var, here is a working example:
constructor(props) {
super(props);
this.state={LID:1}
}
test=(e)=>{
let {LID} = this.state;
this.setState({LID:LID+1})
console.log(this.state.LID);
}
render(){
return(
<button onClick={this.test}> Test</button>
)
}