Vue3 Props v-if sync - vuejs3

I'm making props with vue, but v-if doesn't work, and I'm wondering if the functions I've done are written correctly.
I want the data from props to work synchronously. What are my mistakes made?
What functions does vue props check at startup?
<my-alert type="warning" appearance="outline" showIcon="false">Hello</my-alert>
myAlert.vue
<template>
<div class="block" :class="[typeClass,appearanceClass]">
<div class="alert-icon" v-if="myIcon">
<i>ICON</i>
</div>
</div>
</template>
<script>
const types =
['primary','accent','warn','basic','info','success','warning','error',
];
const appearances =
['border','accent','fill','outline','soft'];
import {defineComponent} from "vue";
import {computed} from "vue";
export default {
props: {
type: {
type: String,
default: 'primary',
required: true,
validator: value => {
return ['primary', 'accent', 'warn', 'basic', 'info', 'success', 'warning', 'error'].includes(value)
}
},
appearance: {
type: String,
default: 'border',
required: true,
validator: value => {
return ['border', 'accent', 'fill', 'outline', 'soft'].includes(value)
}
},
showIcon: {
default: false
}
},
computed: {
typeClass() {
return 'alert-type-' + this.type
},
appearanceClass() {
return 'alert-appearance-' + this.appearance
},
myIcon() {
return this.showIcon;
}
}
}
</script>

From your code what I can see, that you are passing the props shwoIcon like this showIcon="false", which is static passing and eventually pass false value as a string "false" not as Boolean value. So use props like this :shwoIcon="false" I mean use colon before showIcon which make the props dynamic. link
And Also in your my-alert component for showIcon props declear type for the better practice
showIcon: {
type: Boolean,
default: false,
},
Another way is, just simply change your computed property myIcon() to check the string props value, like this below,
myIcon() {
return this.showIcon === "true";
}

Related

Storybook Vue3 - Work with v-model in stories

I have a question regarding Storybook and Vue components with v-models. When writing a story for let's say an input component with a v-model i want a control reflecting the value of this v-model. Setting the modelValue from the control is no problem, but when using the component itself the control value stays the same. I am searching the web for a while now but i can't seem to find a solution for this.
A small example:
// InputComponent.vue
<template>
<input
type="text"
:value="modelValue"
#input="updateValue"
:class="`form-control${readonly ? '-plaintext' : ''}`"
:readonly="readonly"
/>
</template>
<script lang="ts">
export default {
name: "GcInputText"
}
</script>
<script lang="ts" setup>
defineProps({
modelValue: {
type: String,
default: null
},
readonly: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['update:modelValue']);
const updateValue = (event: Event) => {
const target = event.target as HTMLInputElement;
emit('update:modelValue', target.value);
}
</script>
In Storybook:
Does anyone have a solution to make this working?
Thanks in advance!
In my case, I have a custom select input that uses a modelValue prop.
I tried this and worked for me:
at my-component.stories.js:
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
export default {
title: 'Core/MyComponent',
component: MyComponent,
argTypes: { }
}
const Template = (args) => ({
components: { MyComponent },
setup() {
let model = ref('Javascript')
const updateModel = (event) => model.value = event
return { args, model, updateModel }
},
template: '<my-component v-bind="args" :modelValue="model" #update:modelValue="updateModel" />'
})
export const Default = Template.bind({})
Default.args = {
options: [
'Javascript',
'PHP',
'Java'
]
}

Convert options api to composition api for vue3 - v-model binding and watch

I have the following working code for a search input using options API for component data, watch and methods, I am trying to convert that to the composition api.
I am defining props in <script setup> and also a onMounted function.
<template>
<label for="search" class="hidden">Search</label>
<input
id="search"
ref="search"
v-model="search"
class="border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm h-9 w-1/2"
:class="{ 'transition-border': search }"
autocomplete="off"
name="search"
placeholder="Search"
type="search"
#keyup.esc="search = null"
/>
</template>
<script setup>
import {onMounted} from "vue";
const props = defineProps({
routeName: String
});
onMounted(() => {
document.getElementById('search').focus()
});
</script>
<!--TODO convert to composition api-->
<script>
import { defineComponent } from "vue";
export default defineComponent({
data() {
return {
// page.props.search will come from the backend after search has returned.
search: this.$inertia.page.props.search || null,
};
},
watch: {
search() {
if (this.search) {
// if you type something in the search input
this.searchMethod();
} else {
// else just give us the plain ol' paginated list - route('stories.index')
this.$inertia.get(route(this.routeName));
}
},
},
methods: {
searchMethod: _.debounce(function () {
this.$inertia.get(
route(this.routeName),
{ search: this.search }
);
}, 500),
},
});
</script>
What I am trying to do is convert it to the composition api. I have tried the following but I can't get it to work at all.
let search = ref(usePage().props.value.search || null);
watch(search, () => {
if (search.value) {
// if you type something in the search input
searchMethod();
} else {
// else just give us the plain ol' paginated list - route('stories.index')
Inertia.get(route(props.routeName));
}
});
function searchMethod() {
_.debounce(function () {
Inertia.get(
route(props.routeName),
{search: search}
);
}, 500)
}
Any help or pointers in how to convert what is currently in <script> into <script setup> would be greatly appreciated thanks.
I managed to get this working with the below!
<script setup>
import {onMounted, ref} from "vue";
import {Inertia} from "#inertiajs/inertia";
const props = defineProps({
route_name: {
type: String,
required: true
},
search: {
type: String,
default: null
}
});
const search = ref(props.search);
onMounted(() => {
search.value.focus();
search.value.addEventListener('input', () => {
if (search.value.value) {
searching();
} else {
Inertia.get(route(props.route_name));
}
});
});
const searching = _.debounce(function() {
Inertia.get(route(props.route_name), {search: search.value.value});
}, 500);
</script>

vue3 setup emit with function click

i want to run a method when emitting how can i get it?
When handleShow(depth is clicked), I want to run collapsed in the medhod in the setup.
or
I want to trigger the function I will write in setup
<MenuLink
:link="items"
:key="items.title"
#click.stop="handleShow(depth)"
/>
<script>
import {ref} from "vue"
import MenuLink from "./MenuLink";
export default {
name: 'MenuItems',
components: {MenuLink},
props: {
items: {type: Object, required: true},
depth: {Number},
selected: {Number},
},
data() {
return {
opensCollapsed: false
};
},
methods: {
collapsed(dep) {
console.log(dep)
}
},
setup(props, {emit}) {
const showDropdown = ref(false);
const handleShow = (depth) => {
emit('clicked', depth)
}
return {
showDropdown,
handleShow,
}
},
};
</script>
emit should only be used if you want to get an event out of your component to its parent (for example, if your component is a custom button and you want its parent to specify what would happen when clicking on it). Otherwise, you can write the code you want inside of handleShow instead of calling emit. You can also change the function name to whatever you want, just make sure it's the same inside the setup method and in the #click.stop property.
In your case (since you just console.log the result):
<MenuLink
:link="items"
:key="items.title"
#click.stop="handleShow(depth)"
/>
<script>
import {ref} from "vue"
import MenuLink from "./MenuLink";
export default {
name: 'MenuItems',
components: {MenuLink},
props: {
items: {type: Object, required: true},
depth: {Number},
selected: {Number},
},
data() {
return {
opensCollapsed: false
};
},
setup(props, {emit}) {
const showDropdown = ref(false);
const handleShow = (depth) => {
console.log(depth)
// do whatever you want here
}
return {
showDropdown,
handleShow,
}
},
};
</script>

Composition API - Axios request in setup()

I am experimenting with Vue3's Composition API in a Laravel/VueJS/InertiaJS stack.
A practice that I have used a lot in Vue2 with this stack is to have 1 route that returns the Vue page component (eg. Invoices.vue) and then in the created() callback, I would trigger an axios call to an additional endpoint to fetch the actual data.
I am now trying to replicate a similar approach in Vue3 with composition API like so
export default {
components: {Loader, PageBase},
props: {
fetch_url: {
required: true,
type: String,
}
},
setup(props) {
const loading = ref(false)
const state = reactive({
invoices: getInvoices(),
selectedInvoices: [],
});
async function getInvoices() {
loading.value = true;
return await axios.get(props.fetch_url).then(response => {
return response.data.data;
}).finally(() => {
loading.value = false;
})
}
function handleSelectionChange(selection) {
state.selectedInvoices = selection;
}
return {
loading,
state,
handleSelectionChange,
}
}
}
This however keeps on giving me the propise, rather than the actual data that is returned.
Changing it like so does work:
export default {
components: {Loader, PageBase},
props: {
fetch_url: {
required: true,
type: String,
}
},
setup(props) {
const loading = ref(false)
const state = reactive({
invoices: [],
selectedInvoices: [],
});
axios.get(props.fetch_url).then(response => {
state.invoices = response.data.data;
}).finally(() => {
loading.value = false;
})
function handleSelectionChange(selection) {
state.selectedInvoices = selection;
}
return {
loading,
state,
handleSelectionChange,
}
}
}
I want to use function though, so I can re-use it for filtering etc.
Very curious to read how others are doing this.
I have been googling about it a bit, but cant seem to find relevant docu.
All feedback is highly welcomed.
I tried this now with async setup() and await getInvoices() and <Suspense> but it never displayed any content.
So this is how I'd do it, except I wouldn't and I'd use vuex and vuex-orm to store the invoices and fetch the state from the store.
<template>
<div>loading:{{ loading }}</div>
<div>state:{{ state }}</div>
</template>
<script>
import {defineComponent, ref, reactive} from "vue";
import axios from "axios";
export default defineComponent({
name: 'HelloWorld',
props: {
fetch_url: {
required: true,
type: String,
}
},
setup(props) {
const loading = ref(false)
const state = reactive({
invoices: []
})
async function getInvoices() {
loading.value = true;
await axios.get(props.fetch_url).then(response => {
state.invoices = response.data;
}).finally(() => {
loading.value = false;
})
}
return {
getInvoices,
loading,
state,
}
},
async created() {
await this.getInvoices()
}
})
</script>
<style scoped>
</style>
This is of course similar to what you're doing in option 2.

How to provide an array in getStaticPaths?

I learn nextJS and I try to use dynamic routes with a catch-all route. However I'm running into a basic problem, I'm not sure how to supply the data as an array in getStaticPaths.
This is my current code:
import Link from 'next/link';
function test({ variable }) {
return (
<>
<div>
<h1>{variable.var}</h1>
<Link href="/">
<a>← Back</a>
</Link>
</div>
</>
);
}
export async function getStaticProps({ params }) {
const variable = params.variable;
return {
props: {
variable,
},
};
}
export async function getStaticPaths() {
return {
fallback: false,
paths: [
{
params: {
variable: 'testi',
},
},
],
};
}
export default test
And I'm getting the error:
Error: A required parameter (variable) was not provided as an array in getStaticPaths for /test/[...variable]
Any ideas?
Edit:
Forgot to add, my current file name is [...variable].js
Indeed it was stupid:
paths: [
{ params: { variable: ["testi"] } },
]

Resources