I understand ESLINT helping to clean up my code and find it useful however I have found a scenario I am unable to resolve without and dummy console.log statement.
In the scenario, I receive a prop containing modelValue however only modelValue is updated in an event and ESLINT complains that props is defined but never accessed.
<template>
<q-header bordered class="bg-white text-black">
<q-toolbar>
<span class="lt-md">
<q-btn dense flat round icon="menu" #click="toggleLeftDrawer" />
</span>
</q-toolbar>
</q-header>
</template>
<script setup>
/*
emits
*/
const emit = defineEmits(['update:model-value'])
const toggleLeftDrawer = () => {
emit('update:model-value', !props.modelValue)
}
/*
props
*/
const props = defineProps({
modelValue: {
type: Boolean,
default: false
}
})
</script>
modelValue is a state passed down to this component, which from your shared snippet is not used anywhere as of right now.
The emits that you have in other places of your code are actual strings aka not the modelValue variable.
TLDR: modelValue != 'modelValue'
Related
In Vue 2 I would do this:
<script>
export default {
props: ['initialCounter'],
data() {
return { counter: this.initialCounter }
}
}
</script>
In Vue 3 I tried this:
<script setup>
import { ref } from 'vue';
defineProps({ 'initialCounter': Number })
const counter = ref(props.initialCounter)
</script>
This obviously doesn't work because props is undefined.
How can I bind one-way properties to a local variable in Vue 3?
It seems the result of defineProps is not assigned as a variable. check Vue3 official doc on defineProps. Not really sure what is the use case of ref() here but toRef API can be used as well.
import { ref } from 'vue';
const props = defineProps({ 'initialCounter': Number })
const counter = ref(props.initialCounter)
Retrieving a read-only value and assigning it to another one is a bad practice if your component is not a form but for example styled input. You have an answer to your question, but I want you to point out that the better way to change props value is to emit update:modalValue of parent v-model passed to child component.
And this is how you can use it:
<template>
<div>
<label>{{ label }}</label>
<input v-bind="$attrs" :placeholder="label" :value="modelValue" #input="$emit('update:modelValue', $event.target.value)">
<span v-for="item of errors">{{ item.value }}</span>
</div>
</template>
<script setup>
defineProps({ label: String, modelValue: String, errors: Array })
defineEmits(['update:modelValue'])
</script>
v-bind="$attrs" point where passed attributes need to be assigned. Like type="email" attribute/property of a DOM element.
And parent an email field:
<BaseInput type="email" v-model="formData.email" :label="$t('sign.email')" :errors="formErrors.email" #focusout="validate('email')"/>
In this approach, formdata.email in parent will be dynamically updated.
I have this simple file:
<script setup>
import { ref } from 'vue'
import TheHeader from '#/components/_headerbar/TheHeader.vue'
import TheSidebar from '#/components/_sidebar/TheSidebar.vue'
const sidebarState = ref(false)
const onSidebarToggle = () => {
sidebarState.value = !sidebarState.value
}
</script>
<template>
<QLayout view="hHh lpR fFf">
<TheHeader #toggle-sidebar="onSidebarToggle" />
<TheSidebar :sidebar-state="sidebarState.value" />
<QPageContainer>
<RouterView v-slot="{ Component }">
<component :is="Component" />
</RouterView>
</QPageContainer>
</QLayout>
</template>
The sidebarState variable here updates just fine everytime the event toggle-sidebar is fired, but the prop that recieve its value never updates and I just don't know what is happening.
This is the TheSidebar.vue file:
<script setup>
const props = defineProps({
sidebarState: {
type: Boolean,
default: true
}
})
</script>
<template>
<QDrawer
:model-value="props.sidebarState"
show-if-above
side="left"
bordered
>
content
</QDrawer>
</template>
Debugging here I can tell the sidebarState prop from TheSidebar.vue file just never changes, even though the data prop of TheHeader.vue sidebarState changes just normally.
What am I doing wrong?
You shouldn't use .value in template with refs of top-level properties. The value is automatically unwrapped for you (note: make sure the toggle in the top left of the docs is switched from "Options" to "Composition" for link to correctly work).
Simply remove .value and your code should work
<TheSidebar :sidebar-state="sidebarState" />
I have an inline edit I'm working on, and when the input is active I want to set the focus on it. I think the issue though, is that it's within a v-if/v-else so the ref isn't being properly set.
I currently get this error:
Uncaught (in promise) TypeError: Cannot read properties of null (reading 'focus')
Relevant code:
<template>
<SlideUpTransition>
<q-btn
v-if="!isAdding"
flat
class="ans-new-vote noselect full-width q-mt-md"
color="primary"
:ripple="false"
label="+ Create New Answer"
#keydown="toggleIsAdding(true)"
#click="toggleIsAdding(true)" />
<q-input
v-else
ref="target"
v-model="newAnswer"
v-on-click-outside="() => toggleIsAdding(false)"
class="q-mt-md"
label="New Answer"
outlined
dense
#keyup.escape="handleEscape"
#keyup.enter="handleSubmitAnswer"
#keyup.tab="handleSubmitAnswer"
#blur="handleSubmitAnswer">
<template #append>
<q-btn
icon="bi-check-circle"
flat
:ripple="false"
color="primary"
#click="handleSubmitAnswer" />
</template>
</q-input>
</SlideUpTransition>
</template>
<script lang="ts" setup>
import { computed, ref, nextTick } from 'vue';
import { vOnClickOutside } from '#vueuse/components';
import SlideUpTransition from '#/components/transitions/SlideUpTransition.vue';
const isAdding = ref(false);
const target = ref<any>(null);
const newAnswer = ref('');
const toggleIsAdding = (val: boolean) => {
isAdding.value = val;
if (isAdding.value) {
nextTick(() => {
target.value.focus(); // <-- target.value is still null
});
}
};
</script
Is there a better way to handle focusing an input that isn't part of the DOM yet?
Since you are using quasar, I think the best way is to use either QInput's autofocus property, or QForm's autofocus.
Personally, I usually wrap a QInput in a QForm, and set autofocus on the input that I want to be focused. The added benefit is that you don't need to listen to #keyup.enter, but can instead use the form's #submit event (a little bit of future-proofing if you also add a submit button).
I have a form that contains a selector reusable component like this
<template>
<div class="channelDetail" data-test="channelDetail">
<div class="row">
<BaseTypography class="label">{{ t('channel.detail.service') }}</BaseTypography>
<BaseSelector
v-model="serviceId"
data-test="serviceInput"
class="content"
:option="servicePicker.data?.data"
:class="serviceIdErrorMessage && 'input-error'"
/>
</div>
<div class="row">
<BaseTypography class="label">{{ t('channel.detail.title') }}</BaseTypography>
<BaseInput v-model="title" data-test="titleInput" class="content" :class="titleErrorMessage && 'input-error'" />
</div>
</div>
</template>
I'm going to test this form by using vue-test-utils and vitest.
I need to set option props from the script to the selector.
In my thought, this should be worked but not
it('test', async () => {
const wrapper=mount(MyForm,{})
wrapper.findComponent(BaseSelector).setProps({option:[...some options]})
---or
wrapper.find('[data-test="serviceInput"]').setProps({option:[...some options]})
---or ???
});
Could anyone help me to set the props into components in the mounted wrapper component?
The answer is that you should not do that. Because BaseSelector should have it's own tests in which you should test behavior changes through the setProps.
But if you can't do this for some reason, here what you can do:
Check the props passed to BaseSelector. They always depend on some reactive data (props, data, or computed)
Change those data in MyForm instead.
For example
// MyForm.vue
data() {
return {
servicePicker: {data: null}
}
}
// test.js
wrapper = mount(MyForm)
wrapper.setData({servicePicker: {data: [...some data]})
expect(wrapper.findComponent(BaseSelector)).toDoSomething()
But I suggest you to cover the behavior of BaseSelector in separate test by changing it's props or data. And then in the MyForm's test you should just check the passed props to BaseSelector
expect(wrapper.findComponent(BaseSelector).props('options')).toEqual(expected)
I'm trying to control the visibility of a child component from the parent using a prop.
My child component has a visible prop. To avoid mutating the visible prop I assigned it to a local isVisible ref, and I'm using that to conditionally show and hide a form.
But when I try to hide the form using a button with #click="isVisible = false" nothing happens, and I get a console error saying Set operation on key "visible" failed: target is readonly.
It's also confusing why the error messsage is referring to the visible prop because I am using the local isVisible variable instead.
This is the ChildForm.vue (child component):
<template>
<q-form v-if="isVisible">
<q-input v-model="modelValue" />
<q-btn label="Hide The Form" #click="isVisible = false" />
</q-form>
</template>
<script setup lang="ts">
import { ref, toRef } from 'vue';
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false,
},
});
const isVisible = toRef(props, 'visible');
const modelValue = ref('');
</script>
This is the ParentPage.vue (parent component):
<template>
<child-form :visible="showForm" />
<q-btn label="Show the Form" #click="showForm = true" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ChildForm from 'src/components/ChildForm.vue';
const showForm = ref(false);
</script>
toRef() retains the reactive connection to the original source, so modifying isVisible effectively modifies the original prop, which is supposed to be readonly, leading to the warning you observed.
But I think you're actually trying to keep the child's visible prop in sync with the parent's showForm prop, such that updates in the child are automatically reflected in the parent. v-model is the tool for that problem.
In the parent, bind showForm to v-model:visible:
<child-form v-model:visible="showForm">
In the child, emit an update:visible event with the desired value whenever you want to update the parent:
<template>
<q-form v-if="visible">
<q-input v-model="modelValue" /> 👇
<q-btn label="Hide The Form" #click="$emit('update:visible', false)" />
</q-form>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const props = defineProps({
visible: {
type: Boolean,
required: true,
default: false,
},
})
const modelValue = ref('')
</script>
demo