//This is my HomeView
<template>
<div class="home">
<div class="error" v-if="error">Error: {{error.message}}</div>
<div v-else> <BlogList :allBlogs="allBlogs" /> </div>
</div>
</template>
<script>
import { useQuery, useResult } from '#vue/apollo-composable'
import gql from 'graphql-tag'
import BlogList from '../components/BlogList.vue'
// # is an alias to /src
const ALL_BLOGS = gql`
query{
allBlogs{
id
title
author
body
yearPublished
}
}`
export default {
name: 'HomeView',
components: { BlogList },
setup() {
const {result, error } = useQuery(ALL_BLOGS)
// We use use result for nested queries
const allBlogs = useResult(result, null, data => data.allBlogs)
return { allBlogs, error}
}
}
</script>
<style></style>
//This is my BLogList
<template>
<div v-for="blog in allBlogs" :key="blog.id">
<SingleBlog :blog='blog' />
</div>
</template>
<script>
import SingleBlog from '../components/SingleBlog.vue'
export default {
props: ['allBlogs'],
components: { SingleBlog },
}
</script>
<style></style>
//This is my SingleBlog
<template>
<router-link :to="{name: 'Details', params: {id: blog.id}}"><h1>{{blog.title}}</h1></router-link>
<p>{{snippet}}</p>
</template>
<script>
import { computed } from '#vue/runtime-core'
export default {
props: [ 'blog' ],
setup(props) {
const snippet = computed(() => {
return props.blog.body.substring(0,100) + '....'
})
return { snippet }
}
}
</script>
<style></style>
//This is my Details view
<template>
{{blog.title}}
</template>
<script>
import { useQuery, useResult } from '#vue/apollo-composable'
import gql from 'graphql-tag'
export default {
props: ['id'],
setup(props) {
const {result, error} = useQuery(gql`
query allBlogs($id: ID!){
allBlogs(id: $id){
id
title
author
body
yearPublished
}
}
`, props)
const blog = useResult(result)
return {blog, error }
}
}
</script>
<style></style>
In the above code everything works fine, until i get to Details view. The fetching graphql api (django backend) for list of blog I've created works fine. However, trying to see the detail of the blog which has been router-linked to Singleblog is not working. I tried to use the example provided on vue apollo site.
Does anyone have any idea what might be the problem is with my code at Details.vue?
Related
I'm having some trouble accessing data from my GraphQL query.
This is the situation.
In my MenuList.vue when I try to access menu.menuItems.nodes I receive the error
Cannot read properties of undefined (reading 'nodes')
But if I try to access menu.menuItems it works...
Have you any idea why this is happening?
main.js
import { createApp, provide, h } from "vue";
import { ApolloClient, HttpLink, InMemoryCache } from "#apollo/client/core";
import { createApolloProvider } from "#vue/apollo-option";
import App from "./App.vue";
const httpLink = new HttpLink({
uri: "http://XXX.XXX.XXX.XXX/graphql",
});
// Create the apollo client
const apolloClient = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
connectToDevTools: true,
});
// Create a provider
const apolloProvider = createApolloProvider({
defaultClient: apolloClient,
});
const app = createApp({
render: () => h(App),
});
app.use(apolloProvider);
app.mount("#app");
graphql.js
import gql from "graphql-tag";
export const MENU_QUERY = gql`
query MENU_QUERY {
menu(id: "MainMenu", idType: NAME) {
count
id
databaseId
name
slug
menuItems {
nodes {
id
databaseId
title
url
uri
cssClasses
description
label
linkRelationship
target
parentId
}
}
}
}
`;
App.vue
<template>
<div id="app">
<nav class="navbar is-primary" role="navigation" aria-label="main navigation">
<div class="container">
<div class="navbar-brand">
<menu-list></menu-list>
</div>
</div>
</nav>
<router-view/>
</div>
</template>
<script>
import MenuList from './components/MenuList'
export default {
name: 'App',
components: {
MenuList
}
}
</script>
MenuList.vue
<template>
<div>
MENU LIST
<h4 v-if="loading">Loading...</h4>
{{menu.menuItems.nodes}}
</div>
</template>
<script>
import { MENU_QUERY } from "#/graphql";
export default {
name: "MenuList",
data() {
return {
menu: [],
loading: 0,
};
},
apollo: {
menu: {
query: MENU_QUERY,
},
},
};
</script>
Data returned by GraphQL
{
"__typename":"Menu",
"count":2,
"id":"dGVybToz",
"databaseId":3,
"name":"MainMenu",
"slug":"mainmenu",
"menuItems":{
"__typename":"MenuToMenuItemConnection",
"nodes":[
{
"__typename":"MenuItem",
"id":"cG9zdDo2",
"databaseId":6,
"title":null,
"url":"http://XXX/page-3/",
"uri":"/page-3/",
"cssClasses":[
],
"description":null,
"label":"Page 3",
"linkRelationship":null,
"target":null,
"parentId":null
},
{
"__typename":"MenuItem",
"id":"cG9zdDoxMA==",
"databaseId":10,
"title":null,
"url":"http://XXX/page-2/",
"uri":"/page-2/",
"cssClasses":[
],
"description":null,
"label":"Page 2",
"linkRelationship":null,
"target":null,
"parentId":null
}
]
}
}
try:
//add code
<p v-if="menu && menu.menuItems">
{{menu.menuItems.nodes}}
Having some reactive const in "Component A," which may update after some user action, how could this data be imported into another component?
For example:
const MyComponent = {
import { computed, ref } from "vue";
setup() {
name: "Component A",
setup() {
const foo = ref(null);
const updateFoo = computed(() => foo.value = "bar");
return { foo }
}
}
}
Could the updated value of 'foo' be used in another Component without using provide/inject?
I am pretty new in the Vue ecosystem; kind apologies if this is something obvious that I am missing here.
One of the best things about composition API is that we can create reusable logic and use that all across the App. You create a composable functions in which you can create the logic and then import that into the components where you want to use it. Not only does this make your component much cleaner but also your APP much more maintainable. Below is a simple example of counter to show how they can be used. You can find working demo here:
Create a composable function for counter:
import { ref, computed } from "vue";
const counter = ref(0);
export const getCounter = () => {
const incrementCounter = () => counter.value++;
const decrementCounter = () => counter.value--;
const counterPositiveOrNegitive = computed(() =>
counter.value >= 0 ? " Positive" : "Negitive"
);
return {
counter,
incrementCounter,
decrementCounter,
counterPositiveOrNegitive
};
};
Then you can import this function into your components and get the function or you want to use. Component to increment counter.
<template>
<div class="hello">
<h1>Component To Increment Counter</h1>
<button #click="incrementCounter">Increment</button>
</div>
</template>
<script>
import { getCounter } from "../composables/counterExample";
export default {
name: "IncrementCounter",
setup() {
const { incrementCounter } = getCounter();
return { incrementCounter };
},
};
</script>
Component to decrement counter:
<template>
<div class="hello">
<h1>Component To Decrement Counter</h1>
<button #click="decrementCounter">Decrement</button>
</div>
</template>
<script>
import { getCounter } from "../composables/counterExample";
export default {
name: "DecrementCounter",
setup() {
const { decrementCounter } = getCounter();
return { decrementCounter };
},
};
</script>
Then in the main component, you can show the counter value.
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<div class="counters">
<IncrementCounter />
<DecrementCounter />
</div>
<h3>Main Component </h3>
<p>Counter: {{ counter }}</p>
<p>{{ counterPositiveOrNegitive }}</p>
</template>
<script>
import IncrementCounter from "./components/IncrementCounter.vue";
import DecrementCounter from "./components/DecrementCounter.vue";
import { getCounter } from "./composables/counterExample";
export default {
name: "App",
components: {
IncrementCounter: IncrementCounter,
DecrementCounter: DecrementCounter,
},
setup() {
const { counter, counterPositiveOrNegitive } = getCounter();
return { counter, counterPositiveOrNegitive };
},
};
Hope this was somewhat helpful. You can find a working example here:
https://codesandbox.io/s/vue3-composition-api-blfpj
How may I focus on this simple input example?
Should I create one more variable const nameRef = ref(null) or there is more beauty way to resolve this?
<template>
<input ref="name" :value="name" />
</template>
<script>
import {ref, computed} from 'vue';
export default {
props: ['name'],
setup(props) {
const name = computed(() => someTextPrepare(props.name));
// how can I do name.value.focus() for example?
return { name }
}
}
</script>
Try to wrap the name value and ref in reactive property :
<template>
<input :ref="theName.ref" :value="theName.value" />
</template>
<script>
import {ref, computed,reactive} from 'vue';
export default {
props: ['name'],
setup(props) {
const theName=reactive({
value:computed(() => someTextPrepare(props.name)),
ref: ref(null)
})
return { theName }
}
}
</script>
I’m very new to Vue and I begin with Vue 3. I was trying to migrate a template from Vue 2 to Vue 3 so I can start with my project.
I have this plugin file.
Sidebar\Index.ts
import SidebarPlugComp from './SidebarPlugComp.vue'
import SidebarLinkPlugComp from './SidebarLinkPlugComp.vue'
// tiny internal plugin store
const SidebarStore = {
showSidebar: false,
sidebarLinks: [],
displaySidebar (value: boolean) {
this.showSidebar = value
}
}
const SidebarPlugin = {
install (app: any) {
app.config.globalProperties.$sidebar = SidebarStore
app.component('side-bar-plug-comp', SidebarPlugComp)
app.component('sidebar-link-plug-comp', SidebarLinkPlugComp)
}
}
export default SidebarPlugin
Also I have a BaseTopNavLay layout file so I can toggle the sidebar with handleSidebarToggle onclick button method
<template>
\\...
<div class="navbar-toggle d-inline" :class="{toggled: $sidebar.showSidebar}">
<button type="button"
class="navbar-toggler"
aria-label="Navbar toggle button"
#click.prevent="handleSidebarToggle">
<span class="navbar-toggler-bar bar1"></span>
<span class="navbar-toggler-bar bar2"></span>
<span class="navbar-toggler-bar bar3"></span>
</button>
\\ ...
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { ModalComp } from '../components'
export default defineComponent({
name: 'BaseTopNavLay',
components: {
ModalComp
},
// ...
methods: {
handleSidebarToggle (): void {
this.$sidebar.displaySidebar(!this.$sidebar.showSidebar)
},
handleHideSideBar (): void {
this.$sidebar.displaySidebar(false)
},
}
})
</script>
And here is the watch in the App.vue file
<template>
<component :is="this.$route.meta.layout || 'div'">
<router-view />
</component>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Application',
methods: {
toggleNavOpen () {
console.log('here')
let root = document.getElementsByTagName('html')[0]
root.classList.toggle('nav-open')
}
},
/*watch: {
'$sidebar.showSidebar': function(newVal, oldVal) {
console.log(newVal, oldVal)
}
}*/
mounted () {
//#ts-ignore
this.$watch('$sidebar.showSidebar', this.toggleNavOpen)
}
})
</script>
Wherever I test the var this.$sidebar.showSidebar I can access to its value properly. Also, the onclick method is changing the SidebarStore object in Sidebar\Index.ts plugin file.
Can anyone give me a hint what am I missing here? Why the watch doesn't get fired. Thanks in advance.
The problem is that you have not made your $sidebar reactive, and a watch needs to use a reactive variable.
You can keep the store where you have it, but I'd put it into a separate file (store.js) and import where needed, no need to put it on app.config.globalProperties.$sidebar (but that might be a personal preference
// store.js
// using reactive (all values)
export const SidebarStore = Vue.reactive({
showSidebar: false,
sidebarLinks: [],
})
// or using ref (one for each)
// export const showSidebar = Vue.ref(false);
export const displaySidebar = (value: boolean) => {
SidebarStore.showSidebar.value = value;
}
this will make SidebarStore and displaySidebar available anywhere in your code
use like this
<template>
\\...
<div class="navbar-toggle d-inline" :class="{toggled: $sidebar.showSidebar}">
<button type="button"
class="navbar-toggler"
aria-label="Navbar toggle button"
#click.prevent="handleSidebarToggle">
<span class="navbar-toggler-bar bar1"></span>
<span class="navbar-toggler-bar bar2"></span>
<span class="navbar-toggler-bar bar3"></span>
</button>
\\ ...
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { ModalComp } from '../components'
import { SidebarStore, displaySidebar } from '../store'
export default defineComponent({
name: 'BaseTopNavLay',
components: {
ModalComp
},
// ...
methods: {
handleSidebarToggle (): void {
displaySidebar(!SidebarStore.showSidebar)
},
handleHideSideBar (): void {
displaySidebar(false)
},
}
})
</script>
I'm using the single file components, but I can't access a component via another component, follow what I've tried...
<template>
<div id="containerPrincipal" #click.stop="teste">
...
<template>
<script>
/*Other component*/
import flex_div from './elementos/Div.vue'
export default {
name: 'containerPrincipal',
methods : {
teste () {
componente = new flex_div().$mount();
console.log(componente);
}
},
components: {
flex_div
}
}
</script>
Error
_Div2.default is not a constructor
How can I fix this?
To resolve I had to instantiate the Vue again and reference the component...
<template>
<div id="containerPrincipal" #click.stop="teste">
...
<template>
<script>
/*Other component*/
import Vue from 'vue'
import flex_div from './elementos/Div.vue'
let flexdiv = Vue.component('divFlex', flex_div);
export default {
name: 'containerPrincipal',
methods : {
teste () {
let componente = new flexdiv().$mount();
console.log(componente);
}
},
components: {
flexdiv
}
}
</script>
I might be wrong but isn't this what you want to do?
<template><
<div id="containerPrincipal" #click.stop="teste">
<flex-div ref="flex_div"></flex-div>
</div>
<template>
<script>
/*Other component*/
import Vue from 'vue'
import flexdiv from './elementos/Div.vue'
export default {
name: 'containerPrincipal',
methods : {
teste () {
let componente = this.$refs.flex_div
console.log(componente);
}
},
components: {
flexdiv
}
}
</script>