I’m new to React and have a problem I don’t understand. My data is an array of objects with properties of “sketches” (name, number of “links”, number of “nodes”, and other stuff) from a JSON file. I load the data and send some of it to a list component that I filter by the number of links and nodes. All that works fine. My problem is that I want to add an ID number to what I send to the filtered list (for returning a click). Here is the LoadDataFrom Server part of my code:
const MyApp = React.createClass({
loadDataFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
const sketz = [];
for (let i=0; i<data.length; i++) {
const sid = i;
sketz[i] = {sid: sid,
name: data[i].name,
numberOflinks: data[i].numberOflinks,
numberOfnodes: data[i].numberOfnodes
};
}
this.setState({
sketches: sketz
})
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
}, .....
The problem is that, while any property value from data gets to the list component OK, anything else I try to add, such as “sid”, but any other property and value that did not come from data, shows up in the list component as undefined. I have checked sketches in LoadDataFromServer and the assigned IDs are there, but they are gone in the list. I am really not understanding why the sketches object array behaves this way and would like to (1) understand why, and (2) know how do this correctly. Thanks!
Added by request: the list component (actually, two components). It follows the code used in the React docs under the heading of Thinking in React, where it is for a table, not an unordered list. The first console.log (for the ID) gives undefined; the second gives the correct name for each pass through the forEach.
const SketchList = React.createClass({
render: function() {
const rows = [];
const lf = this.props.numlnk; // "link filter"
const nf = this.props.numnde; // "node filter"
this.props.sketches.forEach(function(sketch) {
console.log('in sketch, ID is ' + sketch.sid);
console.log('in sketch, name is ' + sketch.name);
if(lf == 0 || lf == sketch.numberOflinks) {
if (nf == 0 || nf == sketch.numberOfnodes) {
rows.push(<SketchRow
sketch={sketch}
key={sketch.name}
/>);
}
}
});
return (
<div className="row voffset3 divcolor">
<ul className="nobull mar-left list-scroll">
{rows}
</ul>
</div>
);
}
});
const SketchRow = React.createClass({
render: function() {
return (
<li >
{
this.props.sketch.name
}
</li>
);
}
});
Thanks Isiah. OPERATOR ERROR. I was resetting sketches to data in the render, left over from an earlier version where I was only working out the filtered list.
Related
hope you're well. I've been working on a company list component. I am having a problem with updating it in real time, because the axios call I'm sending to 'getById' after it renders is returning only a promise and not the actual data that it is supposed to and I don't have any idea as to why. So when I push the so called new company that I've just added into the array, which is in state, it is only pushing a promise into the array and not the actual Company. I don't have any idea why this is. What the code is supposed to be doing, is it is supposed to be putting the new company into the database, returning the success result, and then I'm using the item from that to make a fresh get call to the axios DB which is supposed to be returning the information I just entered so that I can then insert it into the same array in state that is within the list that is rendered in the company list. However, as I mentioned, only the promise is coming up for some reason.
At one point I was able to get this working, but I did that by essentially calling, 'componentDidMount' after the promise was pushed into the call back clause of the setState funciton of the push function - which was essentially causing the entire component to re-render. I'm a fairly new coder, but my understanding is is that that is a fairly unconventional way to code something, and contrary to good coding methodologies. I believe I should be able to push it into state, and then have it change automatically. I have attached the relevant code below for you to examine. If you believe you need more please let me know. If someone could please tell me why I am getting this weird promise instead of the proper response object, so that I can then insert that into state, I would greatly appreciate it. I've attached some images below the code snippets that I hope will be helpful in providing an answer. I have also left brief descriptions of what they are.
:
class Companies extends React.Component {
constructor(props) {
super(props);
this.state = {
Companies: [],
formData: { label: "", value: 0 },
};
}
componentDidMount = () => {
this.getListCompanies();
};
getListCompanies = () => {
getAll().then(this.listOfCompaniesSuccess);
};
listOfCompaniesSuccess = (config) => {
let companyList = config.items;
this.setState((prevState) => {
return {
...prevState,
Companies: companyList,
};
});
};
onCompListError = (errResponse) => {
_logger(errResponse);
};
mapCompanies = (Companies) => (
<CompaniesList Companies={Companies} remove={remove} />
);
handleSubmit = (values) => {
if (values.companyName === "PPP") {
this.toastError();
//THIS IS FOR TESTING.
} else {
add(values)
.getById(values.item) //I HAVE TRIED IN TWO DIFFERENT PLACES TO GET THE NEW COMPANY IN. HERE
.then(this.newCompanyPush)
.then(this.toastSuccess)
.catch(this.toastError);
}
};
//THIS CODE RIGHT HERE IS THE CODE CAUSING THE ISSUE.
newCompanyPush = (response) => {
let newCompany = getById(response.item); // THIS IS THE OTHER PLACE I HAVE TRIED.
this.setState((prevState) => {
let newCompanyList = [...prevState.Companies];
newCompanyList.push(newCompany);
return {
Companies: newCompanyList,
};
});
};
toastSuccess = () => {
toast.success("Success", {
closeOnClick: true,
position: "top-right",
});
};
toastError = (number) => {
toast.error(`Error, index is ${number}`, {
closeOnClick: true,
position: "top-center",
});
};
This is the axios call I am using.
const getById = (id) => {
const config = {
method: "GET",
url: companyUrls + id,
withCredentials: true,
crossdomain: true,
headers: { "Content-Type": "application/json" },
};
return axios(config).then(onGlobalSuccess).catch(onGlobalError);
};
After the promise is pushed into the array, this is what it looks like. Which is I guess good news because something is actually rendering in real time.
This is the 'promise' that is being pushed into the array. Please note, when I make the same call in post-man, I get an entirely different response, see below.
This is the outcome I get in postman, when I test the call.
guys can you explain why is it not working?
I am GETTING an array from my backend rails API which gives me the data correctly.
Now i made an empty array where i filter my records based on their ID.
It does it correctly ,but whenever i refresh my page my filterRecords methods is not calling it just gives me an empty array.
Here i am looping through that array and writing out the description in my filteredRecords:
<h1
class="content"
v-for="(record, index) of filteredRecords"
:key="index"
:record="record"
:class="{ 'is-active': index === activeSpan }"
>
<strong>{{ record.description }} </strong>
</h1>
This is my filterRecords() in computed:
computed: {
...mapGetters({
id: "id"
}),
filteredRecords() {
return this.records.filter(record => {
return record.template_id === this.id;
});
}
},
this is how i am getting the data from an API:
created() {
if (!localStorage.signedIn) {
this.$router.replace("/");
} else {
this.$http.secured
.get("/api/v1/records")
.then(response => {
this.records = response.data;
})
.catch(error => this.setError(error, "Something went wrong"));
}
},
So how do i make this work so that when i reload my page it gets the records and after that it filters through it ( calls the filteredRecords computedmethod)
Thank you!
EDIT
hello problem was that i used return record.template_id === this.id;
and it should be just return record.template_id == this.id;
since they are different data types.
Try the below code once
computed: {
...mapGetters({
id: "id"
}),
filteredRecords: function () {
let localId = this.id;
let filtered = this.records.filter(record => {
return record.template_id == localId;
});
console.log(localId);
console.log(filtered);
return filtered;
}
},
If this does not work, can you log 'this.id' and see what is getting logged.
It happens because overwriting values of array are not reactive. So Vue doesn't detect the change and doesn't call your computed property.
Try this:
this.records.splice(0, this.records.length - 1, ...response.data)
More about this you can read here: Reactivity in Depth
.splice will work because Vue wraps this method and do some magic under the hood
Edit 1
for(let i = 0; i < response.data - 1; i++) {
Vue.set(this.records, i, response.data[i]);
}
Edit 2
After discussion via chat we figured out that there is a second problem with types (id of the element returned from backend is a string and localId is a number so .filter will return an empty array). The solution is to use == over ===, but I would suggest to use:
record.template_id.toString() === this.id.toString()
Then we will be sure that both id's are strings
I'm trying to write a custom component. And hope I can use it like this
let app = new Vue({
el:'#app',
template:`
<tab>
<tab-item name='1'>
<h1> This is tab item 1</h1>
</tab-item>
<tab-item name='2'>
<h2> This is tab item 2</h2>
</tab-item>
</tab>`,
components:{
tab,
tabItem
}
})
Everything goes fine until you click the button. I got an error from console:
[Vue warn]: You may have an infinite update loop in a component render function.
found in
---> <Tab>
<Root>
I've tried many ways to solve this problem, however, failure always won the debugging competition.
How can I beat this problem?
Here is my code:
let tabItem = {
props:{
name:{
type: String,
required: true
}
},
render(h){
let head = this.$slots.head || ''
let body = this.$slots.default
let tail = this.$slots.tail || ''
return h('div', [
h('div', head),
h('div', body),
h('div', tail)])
}
}
let tab = {
data(){
return {
items:'',
currentView:0
}
},
methods:{
handleTabClick(item){
return ()=>{
let index = this.items.indexOf(item)
this.currentView = this.items[index]
}
},
extractProps(vnode){
return vnode.componentOptions.propsData
}
},
render(h){
this.items = this.$slots.default.filter( node => {
return /tab-item/.test(node.tag)
})
let headers = this.items.map( item => {
let name = this.extractProps(item).name
return h('button', {
on:{
click: this.handleTabClick(item)
}
}, name)
})
let head = h('div', headers)
this.currentView = this.items[0]
return h('div',[head, this.currentView])
}
}
Or any other ways to implement this component?
Thanks a lot for helping me out from the hell.
Thanks for your reply my friends. I'm pretty sure that I get an infinite loop error from the console and my code doesn't work as expected. I don't think using vnode is a good way to implement this component too. However, this is the best solution I can figure out.
This component -- tab should detect its child whose name is tabItem, which is also a component. And tab can extract some data from tabItem. In my case, tab will extract the name property of tabItemn, which will be used to generate the buttons for switching content. Click the button can switch to the relevant content, which is the body of tabItem. In my code, it's currenView.
Like a famous UI library, Element, its tab component can be used like this:
<el-tabs v-model="activeName" #tab-click="handleClick">
<el-tab-pane label="User" name="first">User</el-tab-pane>
<el-tab-pane label="Config" name="second">Config</el-tab-pane>
<el-tab-pane label="Role" name="third">Role</el-tab-pane>
<el-tab-pane label="Task" name="fourth">Task</el-tab-pane>
</el-tabs>
I need to implement one component like this but mine will be more simple. For learning how to do it, I read its source code. Maybe there's not a good way to filter child components. In the source, they use this to filter the el-tab-pane component:
addPanes(item) {
const index = this.$slots.default.filter(item => {
return item.elm.nodeType === 1 && /\bel-tab-pane\b/.test(item.elm.className);
}).indexOf(item.$vnode);
this.panes.splice(index, 0, item);
}
Source Code
I know that I can use $children to access its child components but doing so doesn't guarantee the order of the child components, which is not what I want. Because the order of switching button is important. Detail messages about vnode are not contained in the doc. I need to read the source.
Therefore, after reading the source of Vue, I wrote my code like this then I got my problem.
I finally didn't solve this bug and admit that using this kind of rare code sucks. But I don't know other solutions. So I need you guys help.
Thanks.
You shouldn't change your data in render function, this is wrong
this.items = this.$slots.default.filter( node => {
return /tab-item/.test(node.tag)
})
because it will keep re-rendering, here is a working example for your code, I simply removed items property from data and added new items computed property which returns tab-items nodes.
let tab = {
data(){
return {
currentView:0
}
},
methods:{
handleTabClick(item){
return ()=>{
let index = this.items.indexOf(item)
this.currentView = this.items[index]
}
},
extractProps(vnode){
return vnode.componentOptions.propsData
}
},
computed: {
items(){
return this.$slots.default.filter( node => {
return /tab-item/.test(node.tag)
})
}
},
render(h){
let headers = this.items.map( item => {
let name = this.extractProps(item).name
return h('button', {
on:{
click: this.handleTabClick(item)
}
}, name)
})
let head = h('div', headers)
this.currentView = this.items[0]
return h('div',[head, this.currentView])
}
}
let tabItem = {
name:"tab-item",
props:{
name:{
type: String,
required: true
}
},
render(h){
let head = this.$slots.head || ''
let body = this.$slots.default
let tail = this.$slots.tail || ''
return h('div', [[
h('div', head),
h('div', body),
h('div', tail)]])
}
}
let app = new Vue({
el:'#app',
template:`
<tab>
<tab-item name='1'>
<h1> This is tab item 1</h1>
</tab-item>
<tab-item name='2'>
<h2> This is tab item 2</h2>
</tab-item>
</tab>`,
components:{
tab,
tabItem
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.min.js"></script>
<div id="app"></div>
I'm new to Backbone, and I'm very confused about what's happening when I pass a JSON array (of objects) to a Backbone Collection.
I'm fetching some JSON from a spreadsheet hosted on Google Drive. I'm parsing that data, as the actual data that I want to use in my collection is deeply nested. In my parse function, if I log the length of my desired array, I get 157 (that's correct). I then pass that array into a Backbone Collection, and the length of my collection is 1 (incorrect). It's as though foo.bar.length = 157, but there is only one 'bar' in 'foo', so when I pass foo.bar into the collection, it takes foo.bar and not the contents of foo.bar! Very confused.
Code below...
var table = new TableView();
TableItem = Backbone.Model.extend(),
TableItemCollection = Backbone.Collection.extend( {
model : TableItem,
url : 'https://spreadsheets.google.com/feeds/list/0AjbU8ta9j916dFdjSVg3YkNPUUJnWkZSWjBDWmZab3c/1/public/basic?alt=json-in-script',
sync : function( method, model, options ) {
var params = _.extend( {
type: 'GET',
dataType: 'jsonp',
url: this.url,
processData: false
}, options );
return $.ajax( params );
},
parse : function( resp, xhr ) {
console.log( resp.feed.entry.length ); // THIS LOGS 157
return resp.feed.entry;
}
} ),
TableView = Backbone.View.extend( {
initialize : function ( options ) {
this.collection = new TableItemCollection();
this.collection.on( 'reset', this.parseResponse, this );
this.collection.fetch( {
reset : true,
success : function ( model, response, options ) {
console.log( 'OK' ); // THIS LOGS 'OK'
},
error : function ( model, response, options ) {
console.log( 'ERROR' );
}
} );
},
parseResponse : function () {
console.log( this.collection.length ); // THIS LOGS 1
}
} );
If you dump one of the items returned by Google Spreadsheets, you will see that the data is nested in multiple objects, something like this
{
"id":{"$t":"https://spreadsheets.google.com/feeds/list/..."},
"updated":{"$t":"2013-07-30T12:01:24.000Z"},
"category":[{"scheme":"...","term":"..."}],
"title":{"type":"text","$t":"ACIW"},
"content":{},
"link":[{"rel":"self","type":"application/atom+xml","href":"..."}]
}
In a Fiddle http://jsfiddle.net/nikoshr/kHBvY/
Note how the id property is wrapped in an object "id":{"$t":"https://spreadsheets.google.com/feeds/list/0AjbU8ta9j916dFdjSVg3YkNPUUJnWkZSWjBDWmZab3c/1/public/basic/cokwr"}
Backbone collections don't allow duplicates and duplicates are determined based on their id. All your items are considered duplicates and collapsed into one. If you remove the id or disambiguate it, you will get your 157 items. For example,
parse : function( resp, xhr ) {
var data = resp.feed.entry, i;
console.log(data.length); // THIS LOGS 157
for (i=data.length-1; i>=0; i--)
data[i].id = data[i].id['$t'];
return data;
}
http://jsfiddle.net/nikoshr/kHBvY/2/ for a demo
You probably will have to unwrap all the attributes to use them in a non hair pulling way.
I have a view model setup with an observable array of user objects. The add/remove of items are working correctly but how do I update items? I can find the values using the ko indexOf function.
function User( username, date_inactive, date_active ) {
this.username = username;
this.date_active = date_active;
this.date_inactive = date_inactive;
};
User.prototype.inactivateMe = function() {
json_responses.push( this );
$.getJSON( "url" + this.username, function( json ) {
original = json_response.pop();
//do update here
});
};
userModel = [ ], //Where the loaded usernames are stored.
viewUserModel = {
users: ko.observableArray(userModel)
//.......
//This is how I'm adding users to the array.
addUser: function () {
$.getJSON( "url",
{ username: usern },
function( json ) {
if( json.STATUS != undefined && json.STATUS == 'success' ) {
newuser = new User( json.USERNAME, json.DATE_ACTIVE, json.DATE_INACTIVE );
viewUserModel.users.push( newuser );
}
}
});
}
The values of viewUserModel.users are pushed into the array from a server json reponse.
I want to be able to update the date_active and date_inactive values when the user clicks a button and the server responses with success.
My setup is an adaption from http://net.tutsplus.com/tutorials/javascript-ajax/into-the-ring-with-knockout-js-the-title-fight/
An observable array only tracks changes made to the array (such as pushes and pops), not the data itself. You will need to make date-active and date_inactive observables as #Ianzz has specified.
function User( username, date_inactive, date_active ) {
this.username = username;
this.date_active = ko.observable(date_active);
this.date_inactive = ko.observable(date_inactive);
};
Afterwards in your html, do something such as
<div data-bind="foreach: Users">
<input data-bind="value: date_active"/>
<input data-bind="value: date_inactive"/>
<div>
See fiddle for full example.