ReactJS: this.props.data.map is not a function - javascript

I am trying to get the data from a .json file and displaying the results through a view. Below is the scoreboard.json file.
{
"subject":"scoreboard",
"copyright":"Copyright 2015",
"data":
"games":{
"next_day_date":"2015-07-22",
"modified_date":"2015-08-04T07:34:50Z",
"month":"07",
"year":"2015",
"game":[
{
"game_type":"R",
"double_header_sw":"N",
"location":"Some City, State",
},
{
"game_type":"R",
"double_header_sw":"N",
"location":"Another City, Another State"
}
]
}
I only want the "data" field and not the "subject" or "copyright" fields and am able to do so through the ajax request below.
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
console.log(data.data);
this.setState({data: data.data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
I pass the state down to the receiving file but, when I try to call it I receive a TypeError: this.props.data.map is not a function
render: function() {
var gameDate = this.props.data.map(function(data) {
return (
<h1>{data.modified_date} </h1>
);
});
I want to be able to get all the information that is in the "data" field of the scoreboard.json eventually (i.e. data.games.game[] array). The .map function Does not seem to work for this either.
How would I get the "data" fields (more specifically the modified_date) in the .map function?
Edit: I can log the modified_date by calling data.games.modified_date but, when trying to set the state of data: this.state.data = data.games.game I receive a new warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of "GameDate"

As pointed out by azium my data is an object and not an array like my results were expecting.
To get the "game" array inside the "games" field I set the state as the following:
this.setState({data: data.data.games.game});
To get the other fields I simply created a new state and passed it through a prop i.e. modified_date:
this.state.date = data.data.games.modified_date
The warning: Each child in an array or iterator should have a unique "key" prop. was fixed by adding a "key" property inside the .map method:
render: function() {
var gameDate = this.props.data.map(function(data) {
return (
<h1 key={data.location} >{data.location} </h1>
);
});

Related

How to set Kendo Grid total when datasource.transport.read is set to function?

Kendo version: ^2019.3.1023
Assume I have a datasource like the one below, where the read prop is set to a function, and that the returned data is used in a Kendo Grid.
The success callback of the XHR function/method is passed an object that contains a "page" (array of objects that are part of a paginated query result), and the total result count for the query (count of records on all "pages").
<script>
var dataSource = new kendo.data.DataSource({
transport: {
read: function(options) {
// make JSONP request to https://demos.telerik.com/kendo-ui/service/products
$.ajax({
url: "https://demos.telerik.com/kendo-ui/service/products",
dataType: "jsonp", // "jsonp" is required for cross-domain requests; use "json" for same-domain requests
success: function(result) {
// notify the data source that the request succeeded
options.success(result);
},
error: function(result) {
// notify the data source that the request failed
options.error(result);
}
});
}
}
});
dataSource.fetch(function() {
console.log(dataSource.view().length); // displays "77"
});
</script>
How can I set the grid total?
I tried setting it in the schema, using a TypeScript getter as below, but this doesn't work.
{ schema: { total: this.paginatedRecordsCount }}
I also tried :total="total" in the <kendo-datasource>, and the <kendo-grid>, where total is again TypeScript getter, similarly to this example, but that has no effect (even when I hardcode values).
I found this type defintion, but it's not helpful:
interface DataSourceTransportOptions {
success: (data?: any) => void;
error: (error?: any) => void;
data: any;
}
This is the body of options.success at runtime:
function(data) {
that._ranges = [];
that.success(data, params);
deferred.resolve();
}
Setting total in schema didn't work when paginatedTransactionsCount was a TypeScript getter.
total: this.paginatedRecordsCount
As it turns out, you can use a closure to access a wrapper object (containing count) from the success callback of the method handling XHR (Axios.post in my case) so that the callback can update the count prop in a way that is "visible" to the grid via the schema.total property below.
total: () => this.paginatedRecordsCountWrapper.count

KENDO UI 'Uncaught TypeError: e.slice is not a function'

I have a very weird thing going on.
When the below 2 scripts are added on my view. I got the error
'Uncaught TypeError: e.slice is not a function'
on success block of ajax call.
Html.AppendScriptParts(string.Format("~/Administration/Scripts/kendo/{0}/kendo.data.min.js", kendoVersion));
Html.AppendScriptParts(string.Format("~/Administration/Scripts/kendo/{0}/kendo.web.min.js", kendoVersion));
this is my response from backend.
{"ExtraData":null,"Data":[{"Id":3,"TotalLicense":0,"TotalAvailableLicense":0,"TotalSoldLicense":0,"TotalLicenseAssignedToCustomer":0,"ProductSKU":"SLN-PP-001","ProductName":"Prepaid code - Full Stream License BCM 30 days","LicenseNumber":"BCQH EKDJ LP8E","Runtime":null,"ActivationStart":"01/01/0001","ActivationEnd":"01/01/0001","OwnerName":"Suman Kumar","OwnerEmail":"contact#devodee.com","ShortDescription":null,"OrderNumber":7,"ProductSeName":"prepaid-code-full-stream-license-bcm-30-days","SearchProductSKU":null,"SearchProductName":null,"SearchLicenseNumber":null,"SearchOwnerName":null,"SearchOwnerEmail":null,"SearchOrderNumber":0,"ShowProductSKUFront":false,"ShowProductNameFront":false,"ShowLicenseNumberFront":false,"ShowRuntimeFront":false,"ShowActivationStartFront":false,"ShowActivationEndFront":false,"ShowOwnerNameFront":false,"ShowOwnerEmailFront":false,"ShowShortDescriptionFront":false,"ShowOrderNumberFront":false,"ShowProductSKUBack":false,"ShowProductNameBack":false,"ShowLicenseNumberBack":false,"ShowRuntimeBack":false,"ShowActivationStartBack":false,"ShowActivationEndBack":false,"ShowOwnerNameBack":false,"ShowOwnerEmailBack":false,"ShowShortDescriptionBack":false,"ShowOrderNumberBack":false,"CustomProperties":{}},{"Id":4,"TotalLicense":0,"TotalAvailableLicense":0,"TotalSoldLicense":0,"TotalLicenseAssignedToCustomer":0,"ProductSKU":"SLN-PP-001","ProductName":"Prepaid code - Full Stream License BCM 30 days","LicenseNumber":"DW4W BBAJ TFQX","Runtime":null,"ActivationStart":"01/01/0001","ActivationEnd":"01/01/0001","OwnerName":"Suman Kumar","OwnerEmail":"contact#devodee.com","ShortDescription":null,"OrderNumber":8,"ProductSeName":"prepaid-code-full-stream-license-bcm-30-days","SearchProductSKU":null,"SearchProductName":null,"SearchLicenseNumber":null,"SearchOwnerName":null,"SearchOwnerEmail":null,"SearchOrderNumber":0,"ShowProductSKUFront":false,"ShowProductNameFront":false,"ShowLicenseNumberFront":false,"ShowRuntimeFront":false,"ShowActivationStartFront":false,"ShowActivationEndFront":false,"ShowOwnerNameFront":false,"ShowOwnerEmailFront":false,"ShowShortDescriptionFront":false,"ShowOrderNumberFront":false,"ShowProductSKUBack":false,"ShowProductNameBack":false,"ShowLicenseNumberBack":false,"ShowRuntimeBack":false,"ShowActivationStartBack":false,"ShowActivationEndBack":false,"ShowOwnerNameBack":false,"ShowOwnerEmailBack":false,"ShowShortDescriptionBack":false,"ShowOrderNumberBack":false,"CustomProperties":{}}],"Errors":null,"Total":2}
And when i remove the below JS,
//Html.AppendScriptParts(string.Format("~/Administration/Scripts/kendo/{0}/kendo.data.min.js", kendoVersion));
the error varnished. But both these JS has to be added in the view for some other functionality.
kendo.web.min.js:13 Uncaught TypeError: e.slice is not a function
at init.success (kendo.web.min.js:13)
at i (jquery-1.10.2.min.js:4)
at Object.n.success (kendo.data.min.js:11)
at c (jquery-1.10.2.min.js:4)
at Object.fireWith [as resolveWith] (jquery-1.10.2.min.js:4)
at k (jquery-1.10.2.min.js:6)
at XMLHttpRequest.r (jquery-1.10.2.min.js:6)
Can anyone please elaborate what is going wrong here, am I missing anything?
I too was getting this error when I used kendo.DataSource without a widget to retrieve a single object (serialized as JSON) from the backend. My fix was to add a fake slice() function that returns a clone of the object itself:
schema: {
data: (response: any) => {
// Augment returned data with a slice() method used internally by Kendo DataSource
// in the absence of "schema.model" to obtain a pristine copy of the object.
response.slice = () => JSON.parse(JSON.stringify(response));
return response;
}
}
I was getting this error with a kendoAutoComplete editor control when clearing the control. It didn't happen when first typing characters into the control because it didn't make a server request until there were at least two characters entered. The control used server filtering to retrieve data via a JSON request. The issue was that I was testing for a null or empty filter on the server and in that situation returned an empty JSON object.
The fix was to return an empty JSON array.
In my case, I was using a query in the refresh function of my Kendo ViewModel that was asynchronous and then attempting to pass the value to the function call immediately after to a ListView. It was empty when it was passed due to the AJAX query still being run.
UserProfileNotesViewModel.prototype.initialize = function () {
var _this = this;
if (!this.isInitialized) {
this.container = $("#user-profile-note-container");
this.addModal = $("#user-profile-entry-note-modal");
this.infoViewModel = new InfoViewModel(this);
this.refresh().then(function () { return _this.isInitialized = true; });
let datasource = this.infoViewModel.get("data");
$("#notes-listview").kendoListView({
dataSource: {
data: data,
pageSize: 21
},
schema: {
data: (response) => {
// Augment returned data with a slice() method used internally by Kendo DataSource
// in the absence of "schema.model" to obtain a pristine copy of the object.
response.slice = () => JSON.parse(JSON.stringify(response));
return response;
}
},
template: kendo.template($("#note-search-template").html()),
pageable: true
});
}
};
Instead, I added the notes-listview function call to an always block at the end of the AJAX query. This caused the data to be populated when it was requested.
UserProfileNotesViewModel.prototype.refresh = function () {
var _this = this;
kendo.ui.progress(this.container, true);
return $.ajax({
url: Ccf.Utility.serviceUrl + "User/GetUserNotes/" + _this.options.userId,
method: "GET",
contentType: "application/json; charset=UTF-8",
dataType: "json"
})
.done(function (data, textStatus, jqXHR) {
_this.infoViewModel.set("data", data);
})
.fail(function (data, textStatus, errorThrown) {
_this.infoViewModel.set("messages", Ccf.Utility.getAjaxMessages(data));
})
.always(function (data, textStatus, errorThrown) {
kendo.ui.progress(_this.container, false);
$("#notes-listview").kendoListView({
dataSource: {
data: data,
pageSize: 21
},
schema: {
data: (response) => {
// Augment returned data with a slice() method used internally by Kendo DataSource
// in the absence of "schema.model" to obtain a pristine copy of the object.
response.slice = () => JSON.parse(JSON.stringify(response));
return response;
}
},
template: kendo.template($("#note-search-template").html()),
pageable: true
});
if (!_this.isInitialized) {
kendo.bind(_this.container, _this.infoViewModel);
}
});
};

ReactJS Update ajax call to child component from onClick method from parent

I have an array of data being passed from one json file.
When I click on the list item it triggers a handleClick method that will get the new url and set it to the provided state. That state is then passed onto a child component that makes an ajax call using that link.
The problem is that the ajax call only loads once when the component is mounted and does not make anymore calls after that no matter how many times I click on it.
How would I get the child component to load a new url each time a different item is clicked?
Parent Component:
getInitialState: function() {
return {
gameUrl: ''
};
},
handleClick: function() {
this.setState({
gameUrl: gameUrl
});
},
render: function() {
var gameList = this.props.data.map(function(game) {
var homeTeamName = game.home_team_name;
var awayTeamName = game.away_team_name;
var gameUrl = game.game_directory+'/anotherFile.json';
console.log(homeTeamName+' vs '+awayTeamName+':'+ gameUrl);
var click = this.handleClick;
return (
<li key={game.location} className="list-group-item" onClick={click}>
{homeTeamName}
</li>
);
}, bind);
return (
<div>
<ul>
{gameList}
</ul>
<GameDetail url={this.state.gameUrl}/>
</div>
);
Child Component:
componentDidMount: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({
data: data.data
});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
Implement componentWillReceiveProps method.
It will be called when the props have changed and when this is not an initial rendering.Then update the state depending on the existing and upcoming props.
componentWillReceiveProps: function(nextProps) {
this.setState({
// set something
});
}
Thanks to #WitVault managed to do it here is the revised section:
Instead of componentDidMount, I changed it to componentWillReceiveProps in the child component. Make sure the prop you're passing through is the same in the child and parent component. (i.e. in child component the prop is url, in the parent you're passing it to the same prop <GameDetail url={this.state.gameUrl}/>) Then you access it in the componentWillReceiveProps method via nextProps.url
componentWillReceiveProps: function(nextProps) {
// Access the url prop
var newUrl = nextProps.url;
$.ajax({
url: newUrl,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({
data: data.data
});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},

ReactJS: How to best handle JSON response of newly created object

I have three React components: the first is a container (NoteContainer component) which is responsible for rendering my objects (Note component) in the UI. The data is obtained as JSON via AJAX GET. The last component is a form (NoteForm) which creates new objects (via AJAX POST).
The response from the POST is only the JSON representation of the newly created object, not the JSON for all of the objects.
Should the NoteForm send the JSON response from creating a new object to the NoteContainer which would append it to its state.data and re-render , or should the NoteContainer request the full list of objects and update its state date entirely?
I would presume the first way is better since it does not require requesting data which is already present in the state of NoteContainer. However, I'm still not sure of the "best" way to handle this. Should I give NoteContainer another function, something like addNewNote, which would take the JSON data from the NoteForm and append it to state.data?
I'm new to React so I apologize if this is not a clear question. Here are my components:
var NoteContainer = React.createClass({
getInitialState: function(){
return {data: []};
},
componentDidMount: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data){
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err){
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
render: function(){
var noteNodes = this.state.data.map(function(note){
return (
<Note title={note.title} body={note.body} />
);
});
return (<div className='noteContainer'>{noteNodes}</div>);
}
});
var Note = React.createClass({
render: function(){
return (
<div className="note" >
<h1>{this.props.title}</h1>
<p>{this.props.body}</p>
</div>
);
}
});
var NoteForm = React.createClass({
getInitialState: function(){
return {'title': '', 'body': ''}
},
handleTitleChange: function(e){
this.setState({title: e.target.value});
},
handleBodyChange: function(e){
this.setState({body: e.target.value});
},
handleSubmit: function(e){
e.preventDefault();
var note = {
title: this.state.title,
body: this.state.body};
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
data: note,
success: function(data){
// Send data to NoteContainer?
}.bind(this),
error: function(xhr, status, err){
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
render: function(){
return (
<form>
<input
type='text'
placeholder='Title'
value={this.state.title}
onChange={this.handleTitleChange} />
<textarea onChange={this.handleBodyChange}>
{this.state.body}
</textarea>
</form>
);
}
});
What #xCrZx is suggesting is that you pull the state outside of the individual components and have a one or more top-level stores to maintain state. The simplest (i.e. "vanilla") example of this would be if your NoteContainer was a parent of NoteForm. Then you could simply pass a callback from NoteContainer to NoteForm:
var NoteContainer = React.createClass({
createNote: function() {
...
},
render: function() {
return (
...
<NoteForm createNote={this.createNote}>
...
);
}
});
var NoteForm = React.createClass({
props: {
createNote: React.PropTypes.func.isRequired
},
render: function() {
return (
...
onClick={this.props.createNote}
...
);
}
});
However, that of course only works if the relationship actually exists. Now let's take a look at Reflux, where you create central stores (and actions to go with them) to keep data, and components "listen" to the stores.
var NoteActions = Reflux.createActins([
'createNote',
'getNotes'
]);
var NoteStore = Reflux.createStore({
listenables: [NoteActions],
init: {
// Notes is an empty array by default
this.notes = [];
},
getInitialState: function() {
return {
notes: this.notes
};
},
onCreateNote: function(noteFormData) { ...POST here, save note JSON to this.notes on success... },
onGetNotes: function() { ..GET here for the initial load...}
});
var NoteForm = React.createClass({
render: function() {
...
onClick={NoteActions.createNote(noteFormData)}
...
}
});
var NoteContainer = React.createClass({
mixins: [Reflux.connect(NoteStore)],
componentDidMount: function() {
NoteActions.getNotes();
},
render: function() {
return: function() {
.. same that you have now using this.state.notes
}
}
});
Hope this is starting to make sense. Highly recommend looking through the Reflux (or Redux, similar but different) examples.
The best approach is to keep all notes in global state and add new entities there one by one when needed. It can be achieved with help of global stores, like Redux or Reflux.

Updating a property of an item in an observableArray and persisting to template

I'm using KnockoutJS...
I have an observableArray of movies in my viewModel. The array is updated when a user searches for a movie by title, so a typical json response that would be pushed into the array would look like this...
{data : [{
Id: 12345,
Title: 'Movie1',
Year: 2010,
Character: [{
Name: 'Character1',
Person: { Id: 1, Name: 'Person1' }
},{
Name: 'Character2',
Person: { Id: 2, Name: 'Person2' }
}],
UserMovies: [{
Id: 8
IsWatched: false,
Rating: 3.5,
UserId: 'e1e9c075-1ded-4e7d-8d30-d5d1fbd47103'
}]
}]}
The UserMovies property of each movie represents information specific to that user, like their personal rating or if they've watched the movie yet, etc.
I give the user the ability to "Add" the movie to their collection in the database. When they add the movie to their collection a json response is sent back that has the updated UserMovies property for that movie.
I'm trying to update just that property and then have it persist to the knockout template. Here's my current viewModel...
function viewModel() {
// private properties
var self = this;
// public properties
self.movies = ko.observableArray([]);
self.searchValue = ko.observable();
// public operations
self.search = function () {
self.isLoading(true);
$.getJSON(arguments[0].action, { name: this.searchValue() }, function (data) {
self.movies(data);
});
};
self.add = function (movie) {
$.ajax({
type: 'POST',
url: arguments[1].currentTarget.attributes[1].value,
data: JSON.stringify({ movie: movie }),
contentType: 'application/json',
dataType: 'json',
success: function (data) {
// how to just update UserMovies property and persist to template for the given movie?
}
});
};
}
ko.applyBindings(new viewModel());
I'm not sure how to update the property but as far as persisting the data to the knockout template I think it's because the UserMovies property of each movie isn't itself an observable so if it's updated nothing will automatically change in the template.
I've been reading about the mapping plugin which would make every property an observable. This may fix my persistance problem but I'm still unsure how to update just the UserMovies property whether it's in an observableArray or via the mapping plugin.
Update
I've created a fiddle using the mapping plugin. If I return the whole movie instead of just the UserMovies property it will be much easier to update an item in the array. I'm having some issues getting it to work though and I'm struggling to figure out how to create a mapping for 'key' against the UserMovies property. http://jsfiddle.net/cmBd9/3/
As you yourself correctly note, the fact that the UserMovies property is not an observable means that even if you update it, nothing will happen in the UI. Nonetheless, here is how you could update the (non-observable) UserMovies:
self.add = function (movie) {
$.ajax({
type: 'POST',
url: arguments[1].currentTarget.attributes[1].value,
data: JSON.stringify({ movie: movie }),
contentType: 'application/json',
dataType: 'json',
success: function (data) {
ko.utils.arrayForEach(self.movies(), function(m) {
if (m.Id == movie.Id) {
m.UserMovies = data;
}
});
}
});
};
Definitely check out the mapping plugin. My only concern is that the documentation shows how to initially map an object from JSON, and how later to update the entire object from JSON.
However, it doesn't seem to mention anything about mapping an object from JSON and later coming along and updating only part of that object from JSON, so I'm not sure if that will work or not.
If you have trouble, you could try having the service call for the add method return the entire movie object so that you are more closely following the model espoused by the mapping plugin.
I was able to get it to work with the mapping plugin using mapping options. I set the key for UserMovies to be the Id of each UserMovie so when fromJS() is called it will look to the Id of each UserMovie to tell whether to update or add a new UserMovie
Here's my updated code...
function viewModel() {
// private properties
var self = this;
var mapping = {
'UserMovies': {
key: function (data) {
return ko.utils.unwrapObservable(data.Id);
}
}
};
// public properties
self.movies = ko.mapping.fromJS([]);
self.searchValue = ko.observable();
// public operations
self.search = function () {
self.isLoading(true);
$.getJSON(arguments[0].action, { name: this.searchValue() }, function (data) {
ko.mapping.fromJS(data, {}, self.movies);
});
};
self.add = function (movie) {
$.ajax({
type: 'POST',
url: arguments[1].currentTarget.attributes[1].value,
data: ko.toJSON({ movie: movie }),
contentType: 'application/json',
dataType: 'json',
success: function (data) {
if(data.success) {
ko.mapping.fromJS(data.movie, mapping, movie);
}
}
});
};
}

Categories