i'm creating a web app with a Django server with api from rest_framework and Vue as frontend (Nuxtjs in particular).
Trying to create a "search filter bar" i've got this error and i don't know why:
ERROR [Vue warn]: Property or method "search" is not defined on the instance but
referenced during render. Make sure that this property is reactive,
either in the data option, or for class-based components, by
initializing the property. See:
https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
This is my file.vue
<template>
<div>
<v-text-field v-model="search" label="search conditions" outlined dense></v-text-field>
</div>
<div>
<v-list v-for="condition in filteredConditions" :key="condition.id" >
<v-list-item>
<condition-card :onDelete="deleteCondition" :condition="condition"></condition-card>
</v-list-item>
</v-list>
</div>
</div>
</template>
<script>
import ConditionCard from "~/components/ConditionCard.vue";
export default {
head() {
return {
title: "Conditions list",
search: ""
};
},
components: {
ConditionCard
},
async asyncData({ $axios, params }) {
try {
let query = await $axios.$get(`/conditions/`);
if (query.count > 0){
return { conditions: query.results }
}
return { conditions: [] };
} catch (e) {
return { conditions: [] };
}
},
data() {
return {
conditions: []
};
},
...
...
computed: {
filteredConditions: function(){
return this.conditions.filter((condition) => {
return condition.name.toLowerCase().match(this.search.toLocaleLowerCase())
});
}
}
};
</script>
<style scoped>
</style>
The api is:
{"count":15,
"next":null,
"previous":null,
"results":[{"id":1,
"name":"Blinded",
"description":"A blinded creature can't see...",
"source_page_number":290},
{"id":2,
"name":"Charmed",
"description":"A charmed creature can't...",
...
...
Try to move the search variable from head() to data()
head() {
return {
title: "Conditions list"
}
},
...
data(){
return{
conditions: [],
search : ''
}
}
Related
If i use getcoords() , returns always just the first latitude and longitude for all my array length.
If i use normal syntax {{fire.latitude}},{{fire.longitude}} returns all latitudes and longitudes.
i don't know why, probably a very simple thing
this is the first problem that write, i hope to be was clear.
thanck all for the help .
<template>
<div>
<div v-for="(fire, index) in fires" :key="index">
<!-- {{index}}.{{getCoords()}} -->
{{fire.lat}},{{fire.lon}}
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'FiresList',
data () {
return {
fires: null,
errored: false,
}
},
mounted () {
axios.get('https://storage.googleapis.com/public.storykube.com/start2impact/fires.json')
.then(response => {
this.fires = response.data.map((coords)=> {
return {lat: coords.latitude, lon: coords.longitude, date: coords.date}
})
console.log(this.fires);
})
.catch(error => {
console.log(error)
this.errored = true
})
},
methods: {
getCoords (){
for (var latlon in this.fires){
return [this.fires[latlon].lat, this.fires[latlon].lon]
//OTHERWISE
// for (let i = 0; i <= this.fires.length; i++){
// return [this.fires[i].lat, this.fires[i].lon]
}
}
}
}
</script>
<style scoped>
</style>
I think what you are looking for is this:
<div v-for="(fire, index) in fires" :key="index">
{{index}}.{{getCoords(index)}}
</div>
getCoords(index) {
return [this.fires[index].lat, this.fires[index].lon]
}
I'm trying to get Apollo gql to load more posts after clicking a button. So it would load the next 15 results, every time you click - load more.
This is my current code
import Layout from "./Layout";
import Post from "./Post";
import client from "./ApolloClient";
import { useQuery } from "#apollo/react-hooks"
import gql from "graphql-tag";
const POSTS_QUERY = gql`
query {
posts(first: 15) {
nodes {
title
slug
postId
featuredImage {
sourceUrl
}
}
}
}
`;
const Posts = props => {
let currPage = 0;
const { posts } = props;
const { loading, error, data, fetchMore } = useQuery(
POSTS_QUERY,
{
variables: {
offset: 0,
limit: 15
},
fetchPolicy: "cache-and-network"
});
function onLoadMore() {
fetchMore({
variables: {
offset: data.posts.length
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return Object.assign({}, prev, {
posts: [...prev.posts, ...fetchMoreResult.posts]
});
}
});
}
if (loading) return (
<div className="container mx-auto py-6">
<div className="flex flex-wrap">
Loading...
</div>
</div>
);
if (error) return (
<div className="container mx-auto py-6">
<div className="flex flex-wrap">
Oops, there was an error :( Please try again later.
</div>
</div>
);
return (
<div className="container mx-auto py-6">
<div className="flex flex-wrap">
{data.posts.nodes.length
? data.posts.nodes.map(post => <Post key={post.postId} post={post} />)
: ""}
</div>
<button onClick={() => { onLoadMore() }}>Load More</button>
</div>
);
};
export default Posts;
When you click load more it refreshes the query and console errors
Invalid attempt to spread non-iterable instance
I have been loading for solutions but a lot of the examples are previous or next pages like traditional pagination. Or a cursor based infinite loader which I don't want. I just want more posts added to the list onClick.
Any advise is appreciated, thank you.
Your current POSTS_QUERY it isn't accepting variables, so first you need change this:
const POSTS_QUERY = gql`
query postQuery($first: Int!, $offset: Int!) {
posts(first: $first, offset: $offset) {
nodes {
title
slug
postId
featuredImage {
sourceUrl
}
}
}
}
`;
Now, it will use the variables listed in your useQuery and fetchMore.
And to finish the error is because updateQuery isn't correct, change it to:
function onLoadMore() {
fetchMore({
variables: {
offset: data.posts.nodes.length
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return { posts: { nodes: [...prev.posts.nodes, ...fetchMoreResult.posts.nodes] } };
});
}
});
}
I would suggest useState hook to manage a variable that stores current offset in the dataset, place a useEffect to watch changes to that offset, the offset value in passed as query variable to load data. Remove fetchmore, useEffect hook will do the job.
When user clicks on load more button, you just need to update offset value, that will trigger the query and update data.
const [offset,setOffset] = React.useState(0)
const [results, setResults] = React.useState([])
const { loading, error, data } = useQuery(
POSTS_QUERY,
{
variables: {
offset: offset,
limit: 15
},
fetchPolicy: "cache-and-network"
}
);
React.useEffect(() => {
const newResults = [...results, ...data]
setResults(newResults)
}, [data])
function onLoadMore() {
setOffset(results.data.length)
}
The best example to illustrate what I am trying to develop is a desktop email application.
On the left there is a vertical menu (on a quasar q-drawer).
Next, also on the left, there is a mailing list (on a quasar q-list within a q-drawer).
When each item is selected, the corresponding content is displayed on the right (on a quasar q-page).
Expected operation:
The list is loaded once and when I successively select the various items in the list, only the content on the right should be used and the content updated according to the id sent as a parameter in the request.
Note that the list component is only rendered once; that is, it is not rendered again each time a item is selected from the list and remains visible while the content is displayed on the right
The problem:
When I select the first item in the mailing list it works correctly and as expected, the mail content is displayed on the q-page.
When I select a second item from the list it doesn't work anymore and the following error is displayed on the console:
Uncaught (in promise) NavigationDuplicated {_name:
"NavigationDuplicated", name: "NavigationDuplicated", message:
"Navigating to current location ("/mailcontent") is not allowed",
stack: "Error at new NavigationDuplicated
(webpack-int…node_modules/vue/dist/vue.runtime.esm.js:1853:26)"}
I would appreciate suggestions on how to resolve this issue.
The following code is intended to illustrate the problem in the main part:
Routes: secondlayout is the child of another layout
const routes = [
{
path: "/index",
component: () => import("layouts/AppLayout.vue"),
children: [
{ path: "/home", component: () => import("pages/Home.vue") },
{
path: "secondlayout",
component: () => import("Layouts/MailsPlace.vue"),
children: [
{ path: "/mailcontent", name: 'mailcontent', component: () => import("pages/MailContent.vue") },
]
}
]
}
];
Second layout where the email application (list and content) is rendered with q-drawer and router-view
<template>
<q-layout view="lhh LpR lff" container class=" myclass shadow-2 window-height" >
<q-drawer
style="full-height"
v-model="drawerLeft"
:width="500"
:breakpoint="700"
elevated
content-class="bg-grey-1"
>
<q-scroll-area
class="fit"
style="margin-top:80px">
<q-list separator padding>
<q-separator />
<list-mails
v-for="(mail, index) in mails"
:mail="mail"
:key="mail.id_mail"
:id="index">
</list-mails>
<q-separator />
</q-list>
</q-scroll-area>
</q-drawer>
<q-page-container>
<router-view></router-view>
</q-page-container>
</template>
<script>
export default {
data () {
return {
mails: {},
drawerRight: false,
}
},
/* watch: {
$route(to, from) {
console.log('after', this.$route.path);
}
},
beforeRouteUpdate(to, from, next) {
console.log('before', this.$route.path);
next();
},*/
components: {
'list-mails': require("pages/ListMails.vue").default,
},
created: function() {
this.listMails()
},
methods: {
listMails(){
this.$axios.get("/listmails")
.then(response => {
if (response.data.success) {
this.mails = response.data.mails.data;
} else {
showErrorNotify('msg');
}
})
.catch(error => {
showErrorMessage(error.message);
});
}
}
</script>
Mail list item with mailitemclick method
<template>
<q-item
clickable
v-ripple
exact
#click="mailitemclick(mail.id_mail)"
>
<q-item-section>
<q-item-label side lines="2"> {{ mail.title_mail }}</q-item-label>
</q-item-section>
</q-item>
</template>
<script>
export default {
props: ["mail"],
methods:{
mailitemclick(id){
this.$router.push({
name: 'mailcontent',
params: {id:id}
});
}
}
}
</script>
Mail content
<template>
<q-page class="fit row wrap justify-center tems-start content-start" style="overflow: hidden;">
<div style="padding:5px; margin:0px 0px 20px 0px; min-width: 650px; max-width: 700px;" >
<q-item>
<q-item-label class="titulo"> {{ mail.title_mail }} </q-item-label>
<div v-html="mail.content_mail"></div>
</q-item>
</div>
</q-page>
</template>
<script>
export default {
name: 'mailcontent',
data() {
return {
mail: {},
};
},
created() {
this.$axios.get(`/mailcontent/${this.$route.params.id}`)
.then(response => {
if (response.data.success) {
this.mail = response.data.mail[0])
} else {
showErrorNotify('msg');
}
})
.catch(error => {
showErrorMessage(error.message);
});
}
}
</script>
This happened to me when I had a router-link pointing to the same route. e.g. /products/1.
The user is able to click on the products, but if a product was
already clicked (and the component view was already loaded) and the
user attempts to click it again, the error/warning shows in the
console.
You can solve this by adding catch block.
methods: {
mailitemclick(id) {
this.$router.push({
name: 'mailcontent',
params: {'id': id}
}).catch(err => {});
}
},
But in the mail-content, you need to use watch for calling function and in mounted for first-time calling.
Temp Example -
data() {
return {
mail: {},
test_mails: {
12: {
content_mail: '<div>test 12<div>'
},
122:{
content_mail: '<div>test 122<div>'
}
}
}
},
mounted() {
this.mail = this.test_mails[this.$route.params.id]
},
watch:{
'$route':function () {
this.mail = this.test_mails[this.$route.params.id]
}
}
OR
You can use :to in list-mail to avoild click and catch -
<q-item
clickable
v-ripple
exact
:to="'/mailcontent/'+mail.id_mail"
>
<q-item-section>
<q-item-label side lines="2"> {{ mail.title_mail }}</q-item-label>
</q-item-section>
</q-item>
children: [
{ path: '', component: () => import('pages/Index.vue') },
{
path: "secondlayout",
component: () => import("layouts/mail-place.vue"),
children: [
{ path: "/mailcontent/:id", name: 'mailcontent', component: () => import("pages/mail-content.vue") },
]
}
]
import React, { Component } from 'react';
import axios from 'axios';
I would like to display this on the page:
class ProjectDeatil extends Component {
constructor(props) {
super(props);
this.state = { user: { name: '' } };
}
.
componentDidMount() {
const { match: { params } } = this.props;
axios.get(`http://localhost:8000/api/project/${params.pk}`)
.then(({ data: user }) => {
console.log( user);
this.setState({"User:": user });
});
}
I added her const
render() {
const{ user } = this.state;
return (
Then I tried displaying it again and it still didn't work
<div className="col-md-4 text-white animated fadeInUp delay-2s if " >
<h1>{user.title}</h1>
<h1> Hello Dear</h1>
</div>
I also tried using django rest api and that also didn't work.
</div>
);
}
}
export default ProjectDeatil
You have to fix this line:
this.setState({"User:": user });
to
this.setState({"user": user });
Iterating over an array myarray=[1, 2, 3] works like this:
<template is="dom-repeat" items="[[myarray]]">
<span>[[item]]</span>
</template>
How can I iterate over an object myobject = {a:1, b:2, c:3}?
Here is a complete implementation:
<test-element obj='{"a": 1, "b": 2, "c": 3}'></test-element>
<dom-module id="test-element">
<template>
<template is="dom-repeat" items="{{_toArray(obj)}}">
name: <span>{{item.name}}</span>
<br> value: <span>{{item.value}}</span>
<br>
<hr>
</template>
</template>
<script>
Polymer({
properties: {
obj: Object
},
_toArray: function(obj) {
return Object.keys(obj).map(function(key) {
return {
name: key,
value: obj[key]
};
});
}
});
</script>
</dom-module>
I faced the same problem, but my use-case is a bit more demanding: I need two-way deep binding through the repeat. Plus I cannot afford rewriting the whole tree on each change.
Since I did not find a solution and the polymer team seems to take it slowly on this issue, I made something for the time being. It's written in ES2015, but translating that to vanilla ES5 should be straightforward. Runs in Chrome anyway as is. Or throw it at bable. This page details how. The gist for the purpose of this posting:
vulcanize element.html --inline-script --inline-css | \
crisper -h element.v.html -j element.js;
babel element.js -o element.js
So here we go:
<link rel="import" href="../../bower_components/polymer/polymer.html">
<dom-module id="my-objarray">
<script>
(function() {
'use strict';
class Objarray {
beforeRegister() {
this.is = 'my-objarray';
this.properties = {
array:{
notify:true,
type:Array,
value:function() {return new Array();}
},
object:{
notify:true,
type:Object
}
};
this.observers = ['_onArray(array.*)', '_onObject(object.*)'];
}
_onObject(change) {
if(this._setting) return;
if(change.path == "object") this._rewriteArray();
else this._writeElement(change);
}
_rewriteArray() {
this.splice("array", 0, this.array.length);
for(let i in this.object) {
this.push("array", {key:i, value:this.object[i]});
}
}
_writeElement(change) {
const path = change.path.match(/^object\.([^\.]+)(.*)$/);
const key = path[1];
const objectPath = "object." + key + (path[2] || "");
const id = this._getId(key);
const arrayPath = "array." + id + ".value" + (path[2] || "");
this.set(arrayPath, this.get(objectPath));
}
_getId(key) {
const collection = Polymer.Collection.get(this.array);
for(const element of this.array) {
if((element && element.key) === key) {
return collection.getKey(element);
}
}
}
_onArray(change) {
let path = change.path.match(/^array\.(#\d+)\.([^\.]+)(\.|$)/);
if(!path) return;
let id = path[1], field = path[2];
if(field == "key") throw new Error("my-objarray: Must not change key!");
if(field != "value") throw new Error("my-objarray: Only change inside value!");
this._setting = true;
this.set(this._getPath(change, id), change.value);
delete this._setting;
}
_getPath(change, id) {
let collection = Polymer.Collection.get(change.base);
let index = change.base.indexOf(collection.getItem(id));
let key = change.base[index].key;
return change.path.replace("array." + id + ".value", "object." + key);
}
}
Polymer(Objarray);
})();
</script>
</dom-module>
Usage:
<dom-module id="my-objarray-test">
<template strip-whitespace>
<my-objarray object="{{items}}" array="{{array}}"></my-objarray>
<template is="dom-repeat" items="{{array}}">
<div>
<label>{{item.key}}:</label>
<input type="number" value="{{item.value.data::input}}">
</div>
</template>
</template>
<script>
(function() {
'use strict';
class ObjarrayTest {
beforeRegister() {
this.is = 'my-repeat-test';
this.properties = {
items:{
notify:true,
type:Object,
value:function() {return new Object();}
}
};
this.observers = ['_onItems(items.*)'];
}
ready() {
console.log("my-repeat-test.ready");
this.items = {a:{data:1}, b:{data:2}};
}
_onItems(change) {console.log("test._onItems", change.path);}
}
Polymer(ObjarrayTest);
})();
</script>
</dom-module>
Hope that helps somebody. Presumable polymer now gets the feature like tomorrow :-)
I've been using Object.keys(obj).map(function(prop){return {id:prop, val:obj[prop]}})
Revisiting this to account for issues others have mentioned. This is compatible with all browsers and uses hasOwnProperty.
<template is="dom-repeat" items="[[_toArray(obj)]]">
key: [[item.key]] val: [[item.val]]
</template>
...
_toArray: function(obj, deep) {
var array = [];
for (var key in obj) {
if (deep || obj.hasOwnProperty(key)) {
array.push({
key: key,
val: obj[key]
});
}
}
return array;
}
You need to turn this object into a meaningful array to be able to iterate over it with the dom-repeat.
I have created a myObj property with the initial value. I have then created a property called myObjAsArray which is an empty array. In the ready callback function which is called when the local dom is ready, I am iterating over all of the properties of myObj and adding them to myObjAsArray (see here for how to iterate through an objects properties). You can then iterate over this array with dom-repeat.
<link rel="import" href="bower_components/polymer/polymer.html">
<dom-module id="test-element">
<style>
</style>
<template>
<template is="dom-repeat" items="{{myObjAsArray}}">
name: <span>{{item.name}}</span>
value: <span>{{item.value}}</span>
</template>
</template>
</dom-module>
<script>
Polymer({
is: "test-element",
properties: {
myObj: {
type: Object,
value: function () {
return {
a: 1,
b: 2,
c: 3
};
}
},
myObjAsArray: {
type: Array,
value: function () {
return [];
}
}
},
attached: function () {
var propArray = [];
for (var prop in this.myObj) {
if (this.myObj.hasOwnProperty(prop)) {
propArray.push({name: prop, value: this.myObj[prop]});
}
}
this.myObjAsArray = propArray;
}
});
</script>
Object.keys() doesn't seem to work in IE. So modified the implementation to use _.map instead.
<test-element obj='{"a": 1, "b": 2, "c": 3}'></test-element>
<dom-module id="test-element">
<template>
<template is="dom-repeat" items="{{getKeyValue(obj)}}">
key: <span>{{item.key}}</span>
<br> value: <span>{{item.value}}</span>
<br>
<hr>
</template>
</template>
<script>
Polymer({
properties: {
obj: Object
},
getKeyValue: function(obj) {
return _.map(obj, function(value, key) {
return {
key: key,
value: value
};
});
}
});
</script>
</dom-module>
https://jsfiddle.net/avidlearner/36jnb16d/