Rest call at componentDidMount - reactjs

I am call a REST service in my React component's componentDidMount:
loadData: function () {
return $.getJSON(this.props.server+"/xxxxx/"+this.props.params.promotionid);
},
componentDidMount: function () {
this.loadData().success(function (data) {
console.log("After success");
console.log(data);
if(this.isMounted())
{
this.setState({
prom: data
//release: data.Release
});
}
}.bind(this))
}
This works even without documentation suggestion to include an if(this.isMounted()), there. Plesee note the commented line. We will need to comment this out in order to work. The thing is that if the data I get from the server has a subobject in it, it does not work even with the isMounted(). eg:
{
PromotionId: 1,
Description: "Hello three",
Category: "serviceid",
SpecialID: 23666,
ProjectManager: "Tarkidi Touvouda",
Requestor: "George Klapas",
Readiness: false,
SignOff: false,
SourceDestination: "developement-uat",
Status: "pending",
ReleaseId: 2,
Release: {
ReleaseId: 2,
ReleaseName: "Fall Second",
ReleaseDate: "2015-10-05T00:00:00",
ReleaseDeadline: "2015-10-05T00:00:00",
ModifiedDate: "2015-10-21T12:48:45.753"
},
PromotionAssets: null,
ModifiedDate: "2015-10-21T12:48:45.753"
}
I get the data at render() as follows
var p=this.state.prom;
var rel=p.Release;
I get an error when I try to access rel.ReleaseName that cannot find Releasename of undefined. The only way to make it work is removing the comment you saw (that is to say, having a separate field in state that will hold Release object) and then :
var rel=this.state.release;
Subsequent calls to Release object are successful. Why should I do that? I mean have a separate state field for this?

$.getJSON is an async call, which means that the execution continues while waiting for the response. So your script calls componentDidMount and after that continues with render() without waiting for the results.
Because of this in the first round of render(), the variable this.state.prom is undefined. Due to this a call to this.state.prom.Release throws an error, because you cannot get a property of an undefined object.
A simple solution might be to wait until the AJAX call has been finished an the state has been updated:
render: function() {
if(!this.state.prom) {
return <div>loading ...</div>;
}
// your code goes here
}

Related

Strange bug with Reducer

I maintain an object array in my Context that keeps track of validation errors in my input form. Here's an example of it populated with one object:
validationErrors = [{id: 0, message: 'Already exists', name: 'vin'}]
The message is displayed underneath the input element in question, prompting the user to enter a new value.
When the user does so, an async call is made to check if the new value already exists in the DB. Before that call is made, a call is dispatched to this reducer:
case CLEAR_VIN_INFO: {
return {
...state,
validationErrors: [...state.validationErrors.filter(
validationError => validationError.name !== 'vin' || validationError.id !== action.id)],
vehicles: state.vehicles.map((vehicle: Vehicle) => {
if (vehicle.id === action.id) {
return {
...vehicle,
makeModelYear: ''
};
} else {
return vehicle;
}
})
};
}
It's doing a few things but the main thing I want to focus on is that this specific validation error, with id: 0 and name: vin are removed from validationErrors. Watching what's happening in Chrome DevTools, I can indeed confirm that it is being removed. In other words, validationErrors is set to an empty array.
But upon returning from the async call, the following code produces unexpected results:
console.log(validationErrors.find(error => error && error.name === 'vin' && error.id === vehicle.id));
It reveals the following: {id: 0, message: 'Already exists', name: 'vin'}
How is this possible?
By the way, checking validationErrors in Chrome DevTools again shows that it's still empty!
My intuition is telling me that this bug is due to a previous version of validationErrors being referenced instead. I've never encountered this before though, so am not sure what to do.
Any ideas?
Update: As a sanity check of sorts, I made a call to this reducer instead:
case CLEAR_ERRORS: {
return {
...state,
validationErrors: []
};
}
This one absolutely clears validationErrors before the async call is made. Yet, upon returning from the async call, validationErrors is still populated! Now I'm completely baffled.
Try to fetch the state and access validationErrors inside your async call, then it should be good... but need more explanation in your question.

React's setState mutates variable

Very strange behavior, most probably a bug in my code:
I get an API response object (json) and when I setState using one of the object's properties as value, the object changes. I thought this was a problem with the backend or fetch method, but it has been proven not to be.
Code is generally as follows:
fetch(this.props.url, {
method: 'POST',
body: JSON.stringify({
class: 'property',
action: 'view',
objectId: this.props.element.id,
token: this.props.token,
}),
})
.then(response => response.json())
.then(json => {
this.setState({
property: json.property
});
console.log(json);
})
What I am supposed to get:
{
property: {
units: {
{
id: 31,
...
},
{
id: 33,
...
}
}
}
}
{
What I actually get:
property: {
units: {
{
id: 33,
...
},
{
id: 33,
...
}
}
}
}
Needless to say, response from backend is with the proper ids, which are unique.
Any ideas what I might have done wrong? This is not supposed to happen, is it? How does the json variable change?
Thanks.
Ok, found my mistake. It is very confusing and honestly, I believe this should not happen by design, but anyways:
Once I set state.property to json.property, I pass the state.property as a prop to a child element. Within that child element I am doing some filtering and mapping using props.property and at one point I've mistakenly used a single equal (=) sign instead of double (==) when comparing the ids in a filter function.
Apparently this sets the wrong id and goes all the way back to the initial json response (so, filter function -> child element props -> ?parent element state? -> json response). So, be ccareful with your filter functions, unless you want to spend a few days tracking the impossible.

Jaydata saveChanges() counts tracked / changed entities, but doesn't send a batch request (with OData v4 Provider and Web Api)

by working with jaydata i am adding entities to a tree structure with deep nesting of entity-objects.
I attach the upper entity to the context and edit/add/remove related child entities. At the end i use saveChanges() with a promise.
The count-value passed to the promise tells that all changed entities have been counted correctly but saveChanges() didn't execute a batch request, persisting these entities.
So it feels like nothing else happens, but counting entities.
I post a small code example. I am quite sure, that the references of the entites are set correctly. (Working with jaydata, odata v4, web api and angularjs)
Is someone else having this problem with jaydata and found the reason?
Thanks for your help. =)
Greetings Paul
// Attach upper entity
DataService.jaydata.attach(viewModel.currentSkillTree.entity);
// Generating new entities
var newSkill = new DataService.jaydata.Skills.elementType({
Id: undefined,
Name: 'New skill',
Levels: [],
IconId: 47,
SkillTreeUsage: []
});
var newSkillLevel = new DataService.jaydata.SkillLevels.elementType({
Id: undefined,
ShortTitle: 'New level',
Skill: newSkill,
SkillId: undefined,
Level: 1,
RequirementSets: []
});
var newRequirementSet = new DataService.jaydata.RequirementSets.elementType({
Id: undefined,
SkillLevel: newSkillLevel,
SkillLevelId: undefined,
SkillTree: undefined,
SkillTreeId: viewModel.currentSkillTree.entity.Id,
});
var newSkillTreeElement = new DataService.jaydata.SkillTreeElements.elementType({
Id: undefined,
SkillTree: undefined,
SkillTreeId: viewModel.currentSkillTree.entity.Id,
Skill: newSkill,
SkillId: undefined,
Position: { X: x, Y: y }
});
// Completing object-references
viewModel.currentSkillTree.entity.Elements.push(newSkillTreeElement);
newSkill.Levels.push(newSkillLevel);
newSkill.SkillTreeUsage.push(newSkillTreeElement)
newSkillLevel.RequirementSets.push(newRequirementSet);
// Saving
DataService.jaydata.saveChanges()
.then(function (cnt) {
console.log('Saved entities:', cnt);
// The cnt-result in console is 4
// But no request was executed, nothing was saved
}, function (exception) {
console.log(exception); // Also no exception was thrown
});
So to not be that unkind.
The solution to solve the problem above to me, since i tried nearly every combination with entities (adding, attaching, .save(), .saveChanges(), object-references etc, figuring out it doesn't make sense anyway, it just acted the same way and seems to be so buggy), ended up within a workaround acting with classic nested async calls.
The solution was to save entities seperately within nested promises and to turn off the batch behavior of jaydata, to avoid double requests.
You can find the option within $data.defaults
$data.defaults.OData.disableBatch = true;
As result i am dealing now with good old nasty pyramids of doom, which at least gave the possibility back to save entities in the right order, with full control, the way the api needs it.
// Saving new SkillLevelRequirement connection
if (isConnectionGiven === false) {
// The first level of source skill where the target-skill-requirement will be added
var sourceSkillLevel = Enumerable
.From(sourceSkill.Levels)
.FirstOrDefault(null, function (x) {
return x.Level === 1;
});
// The last level of the target-skill to solve
var targetSkillLevel = Enumerable
.From(targetSkill.Levels)
.FirstOrDefault(null, function (x) {
return x.Level === targetSkill.Levels.length;
});
// First set of first level from source skill (will be used to add skilllevel-requirement)
var firstRequirementSet = sourceSkillLevel.RequirementSets[0];
// New RequirementAsignment
var newRequirementAssignment = new DataService.jaydata.RequirementAssignments.elementType({
RequirementSetId: firstRequirementSet.Id,
Order: 1
});
// New Requirement
var newRequirement = new DataService.jaydata.Requirements.elementType({
Title: requirementTypes.SKILL_CONNECTION,
RequirementOfIntId: undefined,
RequirementOfBoolId: undefined,
RequirementOfSkillLevelId: 0
});
// New RequirementOfSkillLevel
var newRequirementOfSkillLevel = new DataService.jaydata.RequirementsOfSkillLevel.elementType({
SkillLevelId: targetSkillLevel.Id,
});
// Loading symbol
showBusyIndicator();
newRequirementOfSkillLevel.save()
.then(function () {
newRequirement.RequirementOfSkillLevelId = newRequirementOfSkillLevel.Id;
newRequirement.save()
.then(function () {
newRequirementAssignment.RequirementId = newRequirement.Id;
newRequirementAssignment.save()
.then(function () {
// Loading symbol will be closed after tree reloaded
reloadCurrentTree();
}, function (exception) {
showJayDataExceptionModal(exception);
});
}, function (exception) {
showJayDataExceptionModal(exception);
});
}, function (exception) {
showJayDataExceptionModal(exception);
});
}
}
#jaydata developers: Thanks for 42 new grey hairs. I'm still at the point where i think i am using your tool wrong and jaydata could do so much better. Better up your documentation, sieriously. No desserts for you today.

React error message missing

I have a very simple React case where I have an error in the render function and nothing gets logged when the state gets updated to contain a bad value.
The error is that after the state is updated to contain a 'stuff' value that is not an array, which means that calling join(", ") on the 'stuff' value should fail, but nothing happens. React only keeps the data previously set in the getInitialState-function.
Am I doing something wrong here to loose errors messages or is this just how React works?
var someComp = React.createClass({
getInitialState: function () {
return {persons: [
{name: "Jane", stuff: ["a", "b"]},
{name: "Jim", stuff: ["c", "d"]}
]}
},
componentDidMount: function () {
superagent.get('/api/tags')
.end(function (res) {
// The res.body contains a stuff-value that is not an array.
this.setState({persons: res.body});
}.bind(this));
},
render: function () {
return R.table({}, [
R.thead({},
R.tr({}, [
R.th({}, "Name"),
R.th({}, "List of stuff")
])),
R.tbody({},
this.state.persons.map(function (person) {
return R.tr({}, [
R.td({}, person.name),
// If person.stuff is not an array, then no error is logged..!
R.td({}, person.stuff.join(", "))]);
}))
])
}
});
In my experience with React, it logs much more useful error messages if you build things step-by-step. Do that R.td({}, person.stuff.join(", "))] before the return statement, then use that result later on. If person.stuff isn't an array you'll get a better error message then.
More to the point, why do you want React to throw an error? If it's for development purposes, there are easier ways to check than waiting for error messages from React. Otherwise, shouldn't you be including a check to see if person.stuff is an array before attempting to use it as one?

I'm not calling $apply explicitly but still get Error: [$rootScope:inprog] $apply already in progress

In a angular factory I have a method to create a new item, which has a connection to a user and a price to add to that users "items" array (like a shopping cart). So I have to see if the user is present in my the local users array if not then on the server and if not then create the user.
Code looks like this:
var saveItem = function (item) {
var user = filterUserById(item.ownerId);
if (user) {
user.createItem(item);
} else {
repository.getUserById(item.ownerId).then(
function (serverUser) {
var userViewModel = repository.getUserViewModel(serverUser);
userViewModel.createItem(item);
users.push(userViewModel);
}
, function () {
user = {
id: item.ownerId,
items: [
createItemDto(item)
]
};
repository.createUser({ id: user.id }, user);
users.push(repository.getUserViewModel(user));
});
}
};
No matter which of the "cases" occurs (user was found localy, on the server or was created and added) I get an error:
Error: [$rootScope:inprog] $apply already in progress
http://errors.angularjs.org/1.3.0-beta.18/$rootScope/inprog?p0=%24apply
I recon this may have to do with the fact that I'm using resources in my repository, but I don't think resource should (since it's a part of angular..). Here's the user.createItem method, code:
user.createItem = function (item) {
var resource = userResource
, itemDto = createItemDto(item)
, command = [{
Type: 'add',
Name: 'items',
Value: itemDto
}];
resource.createItem({ id: item.ownerId }, command);
this.items.push(itemDto);
};
Y U NO WERK!? PLS HLP! :'(
P.S. I don't have any explicit calls to apply, compile or digest anywhere in my code.
Found the problem! I had put a small code line to set focus on the correct input after the item was added and form was emptied. This consisted of a
$('selector').focus();
This was colliding with digest cycle... Solution:
$timeout($('selector').focus());
Try wrapping your call to user.createItem(item) in a $timeout function:
$timeout(function() {
user.createItem(item);
}, 0);
It's possible you could be triggering some other call to $scope.$apply() some other way.
Alternatively, try using $scope.$evalAsync(function())
Here's some good info: inprog

Resources