How do I use vue-dragscroll in quasar? - vuejs3

I want to use vue-dragscroll in my quasar project. I have created a boot file called scrollbar.js and it looks like this
import { boot } from "quasar/wrappers;"
import { createApp } from "vue";
import { dragscrollNext } from "vue-dragscroll";
import App from "./App.vue";
const app = createApp(App)
app.directive("dragscroll", dragscrollNext);
app.mount("#app");
export default boot(async(/* { app, router, ...} */) => {});
In my quasar.config.js file I did this:
boot: ["scrollbar"],
And in my component I have this:
<script setup>
import useTouch from "src/composables/useTouch.js";
defineProps({
items: Array,
})
const { isTouchDevice } = useTouch();
<script>
<template>
<div v-dragscroll="!isTouchDevice">
<ProductCard v-for="item in items" :key="item.id" :item="item" />
<div>
<template>
The useTouch.js composable looks like this:
export default () => {
const isTouchDevice = computed(
() =>
"ontouchstart" in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0
);
return {
isTouchDevice,
};
};
This doesn't work and when I run, it gives a blank page. What am I doing wrong here, how to make this work?

Related

nextjs 13 generateStaticParams used with next/header causes unhandled runtime error in dev mode

I am working on a new project, and recently used nextjs13 for my frontend application.
When using the function generateStaticParams with the next/header library function headers(),
I get an error in dev mode.
Error occured during dev mode
But when the frontend is on using next build / next start, the error does not appear.
The main reason I am using the next/header library is due to next-auth, to gain access to cookies.
generateStaticParams is in the app/detail/[questionId]/page.tsx file
next/headers is in app/layout.tsx file
app/page.tsx
import React from "react";
import QuestionCard from "../components/Card/QuestionCard";
import Carousel from "../components/Carousel/Carousel";
import HomeNavBar from "../components/HomeNavBar/HomeNavBar";
import { ICarousel } from "../types/carousel";
import TabNavigator from "../components/TabNavigator/TabNavigator";
const getGoogleSession = async () => {};
const getQuestionList = async () => {
const response = await fetch(`https://pioneroroom.com/questionlist`);
const data = await response.json();
return data;
};
const page = async ({ Question }: any) => {
// const imageArr = await getCarouselImages();
const data = await getQuestionList();
return (
<div className="main">
<HomeNavBar />
{/* <Carousel carousel={imageArr} /> */}
<div className="contentbody">
{data.data.map((e: any) => {
return <QuestionCard key={e.questionId} question={e} />;
})}
</div>
<TabNavigator activeLink={""} />
</div>
);
};
export default page;
app/layout.tsx
import { Roboto, Noto_Sans_KR } from '#next/font/google';
import NavBar from '../components/HomeNavBar/HomeNavBar';
import '../styles/globals.css';
import SessionContainer from '../components/Providers/SessionProvider';
import '../styles/globals.css';
import { unstable_getServerSession } from 'next-auth';
import { getSession } from '../utils/helper/session';
import { cookies, headers } from 'next/headers';
import HomeNavBar from '../components/HomeNavBar/HomeNavBar';
import TabNavigator from '../components/TabNavigator/TabNavigator';
const noto = Noto_Sans_KR({
weight: '400',
fallback: ['Roboto'],
subsets: ['latin'],
});
const RootLayout = async ({ children }: any) => {
const { segment } = children.props.childProp;
const session = await getSession(headers().get('cookie') ?? '');
const nextCookies = cookies();
return (
<html className={noto.className}>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>asdf</title>
</head>
<body>
<SessionContainer session={session}>{children}</SessionContainer>
</body>
</html>
);
};
export default RootLayout;
app/detail/[questionId]/page.tsx
import { headers } from 'next/headers';
import React, { use } from 'react';
import { getSession } from '../../../utils/helper/session';
const fetchPost = async (id: any) => {
const res = await fetch(`https://pioneroroom.com/questionlist/${id}`);
return await res.json().then((res) => res.data);
};
const DetailIdPage = async ({ params }: any) => {
console.log('params.questionId', params.questionId);
const post = await fetchPost(params.questionId);
return (
<div>
<p>{JSON.stringify(post)}</p>
</div>
);
};
// BUG: generateStaticParams 함수가 현재 dev 모드에서 동작하지 않음.
// dynamic headers( next/headers )의 cookie등을 불러올 때 오류를 일으키고,
// dev mode에서 이 함수와 결합하여 사용하면 dynamic server usage: headers error 발생함.
/*
export async function generateStaticParams() {
const res = await fetch('https://pioneroroom.com/questionlist');
const data = await res.json();
const arr = data.data.map((e: any) => {
console.log('map', e.questionId);
return {
questionId: String(e.questionId),
};
});
return arr;
}
*/
export default DetailIdPage;
Erasing either both of the code (generateStaticParams or next/header) solves the problem. No errors occuring in dev mode.

Vue 3 composition test method called by child component event

My goal is to test that the method selectVirtualDevices is called when the child component emits the event.
Parent component
<template>
<ChildComponent
#select-devices="selectVirtualDevices"
/>
</template>
<script lang="ts" setup>
const selectVirtualDevices = (devices) => {
some other magic happening here
}
</script
Child component
<script setup lang="ts">
import { computed, ref } from 'vue'
const emit = defineEmits(['selectDevices'])
const selectedDevices = computed({
get() {
return state.selectedDevices
},
set(device) {
emit('selectDevices', devices)
},
})
</script
Test
import { describe, it, expect, vi } from 'vitest'
import { mount } from '#vue/test-utils'
import Parent from './Parent.vue'
import ChildComponent from './ChildComponent.vue'
describe('All Devices', () => {
const wrapper = mount(Parent)
it('should be mounted', () => {
expect(wrapper.html()).toBeTruthy()
})
it('should call selectVirtualDevices', () => {
const spy = vi.spyOn(wrapper.vm, 'selectVirtualDevices')
wrapper.findComponent(ChildComponent).vm.$emit('selectDevices')
expect(spy).toHaveBeenCalled()
})
})
The test fails with:
AssertionError: expected "selectVirtualDevices" to be called at least once
❯ Proxy.methodWrapper ../node_modules/chai/lib/chai/utils/addMethod.js:57:25
❯ apps/line-planning/components/AllDevices.spec.ts:35:16
33| wrapper.findComponent(DevicesDataTable).vm.$emit('selectDevices')
34|
35| expect(spy).toHaveBeenCalled()
| ^
36| })
37| })
What am I doing wrong?

Vue 3 Composition API: Update Child components props dynamically when values update from the parent component

I am trying to update a prop value when the data from the parent component gets updated and passes through the prop. The parent value always updates but does not update or re-renders in the child component when I pass it down. It passes to the prop the first time the child component is accessed but not when the data is updated in the parent component.
Below is the parent component:
<script setup>
import { inject, watchEffect, ref } from "vue";
import ChildComponent from "#/components/ChildComponent.vue"
const { state } = inject("store");
const cart = ref(state.cart);
watchEffect(() => (cart.value = state.cart));
</script>
<template>
<ChildComponent
v-for="(item, index) in cart?.items"
:key="index"
:cartItem="item"
/>
</template>
Below is the child component (only logs on the first load, never loads again):
<script setup>
import { ref, watchEffect } from "vue";
const { cartItem } = defineProps({
cartItem: !Object
});
const item = ref(cartItem);
watchEffect(() => {
console.log(item.value)
});
</script>
I have tried using Watch in many ways but it does not detect the old or the new values. It does not log any outputs
Example child component using watch:
<script setup>
import { ref, watch } from "vue";
const { cartItem } = defineProps({
cartItem: !Object
});
const item = ref(cartItem);
watch(() => item.value, (oldValue, newValue) => {
console.log(oldValue)
console.log(newValue)
});
</script>
I ended up solving the solution by using a v-if to rerender the child component.
<script setup>
import { inject, watchEffect, ref } from "vue";
import ChildComponent from "#/components/ChildComponent.vue"
const { state } = inject("store");
const cart = ref(state.cart);
const render = ref(true);
// Checks when the cart changes from the store
watchEffect(() => {
if(cart.value) {
render.value = true
}
else {
render.value = false
}
};
</script>
<template>
<div v-if="render">
<ChildComponent
v-for="(item, index) in cart?.items"
:key="index"
:cartItem="item"
/>
</div>
</template>
I had the same issue and it was frustrating, sometimes I had to do a workaround to get what I need, but try this inside the child component:
<script>
import { ref, watch } from "vue";
export default {
props: {
cartItem: {
type: !Object,
},
},
setup(props) {
const item = ref(null);
watch(props, () => {
item.value = props.cartItem;
});
return { item }
}
</script>

#vue/test-utils how to test v-if in vue3 when the parameter is imported

here are all code. parameter isInApp is imported from tools.ts. I had mount the vue component and add options ,In this case, how to mock isInApp value to finish the test
// a.vue
<template>
<div class="test" v-if="isInApp">test</div>
</template>
<script lang="ts">
import { isInApp } from './tools'
export default {
setup() {
return {
isInApp,
}
},
}
</script>
//tools.ts
export const isInApp = navigator.userAgent.indexOf('baidu') > -1
// a.spec.ts
import { mount } from '#vue/test-utils'
import a from './a.vue'
test('test',async ()=>{
const wrapper = mount(a,{
data(){
return{
isInApp: true, // I had set the data here, but it doesn't work, how to fixed it ?
}
}
})
expect(wrapper.find('.test').exists()).toBeTruthy() // Received: false
})

Dynamic component in Vue3 Composition API

A simple working example of a Vue2 dynamic component
<template>
<div>
<h1>O_o</h1>
<component :is="name"/>
<button #click="onClick">Click me !</button>
</div>
</template>
<script>
export default {
data: () => ({
isShow: false
}),
computed: {
name() {
return this.isShow ? () => import('./DynamicComponent') : '';
}
},
methods: {
onClick() {
this.isShow = true;
}
},
}
</script>
Everything works, everything is great. I started trying how it would work with the Composition API.
<template>
<div>
<h1>O_o</h1>
<component :is="state.name"/>
<button #click="onClick">Click me !</button>
</div>
</template>
<script>
import {ref, reactive, computed} from 'vue'
export default {
setup() {
const state = reactive({
name: computed(() => isShow ? import('./DynamicComponent.vue') : '')
});
const isShow = ref(false);
const onClick = () => {
isShow.value = true;
}
return {
state,
onClick
}
}
}
</script>
We launch, the component does not appear on the screen, although no errors are displayed.
You can learn more about 'defineAsyncComponent' here
https://labs.thisdot.co/blog/async-components-in-vue-3
or on the official website
https://v3.vuejs.org/api/global-api.html#defineasynccomponent
import { defineAsyncComponent, defineComponent, ref, computed } from "vue"
export default defineComponent({
setup(){
const isShow = ref(false);
const name = computed (() => isShow.value ? defineAsyncComponent(() => import("./DynamicComponent.vue")): '')
const onClick = () => {
isShow.value = true;
}
}
})
Here is how you can load dynamic components in Vue 3. Example of dynamic imports from the icons collection inside /icons folder prefixed with "icon-".
BaseIcon.vue
<script>
import { defineComponent, shallowRef } from 'vue'
export default defineComponent({
props: {
name: {
type: String,
required: true
}
},
setup(props) {
// use shallowRef to remove unnecessary optimizations
const currentIcon = shallowRef('')
import(`../icons/icon-${props.name}.vue`).then(val => {
// val is a Module has default
currentIcon.value = val.default
})
return {
currentIcon
}
}
})
</script>
<template>
<svg v-if="currentIcon" width="100%" viewBox="0 0 24 24" :aria-labelledby="name">
<component :is="currentIcon" />
</svg>
</template>
You don't need to use computed or watch. But before it loads and resolved there is nothing to render, this is why v-if used.
UPD
So if you need to change components (icons in my case) by changing props use watchEffect as a wrapper around the import function.
watchEffect(() => {
import(`../icons/icon-${props.name}.vue`).then(val => {
currentIcon.value = val.default
})
})
Don't forget to import it from vue =)
The component should be added to components option then just return it name using the computed property based on the ref property isShow :
components:{
MyComponent:defineAsyncComponent(() => import("./DynamicComponent.vue"))
},
setup(){
const isShow = ref(false);
const name = computed (() => isShow.value ? 'MyComponent': '')
const onClick = () => {
isShow.value = true;
}
}
Instead of string you should provide Component
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>
<template>
<component :is="Foo" />
<component :is="someCondition ? Foo : Bar" />
</template>

Resources