Input checbox v-model not checking with async data - vue.js

I have a component that at the start of it, fetchs data from database. And it supose to check the input if the value is true or false. But the problem is that is not checking when its true. I tried :checked and it works, but i need the v-model because of the two way binding
code:
<input type="checkbox" v-model="data">
const data = ref(false)
onBeforeMount(async () => {
await data = fetchingData()
})
I didnt write the hole code, because the code its on another computer, this was from head. But i am having poblems with v-model not checking. If i use :checked it works like a charm
Maybe this is a rookie mistake, but without using :checked with :change i am not seing any solution.

You should use data.value instead of data for the reactivity. For the demo purpose I am directly assign the value as true. You can replace that with the API call code.
Live Demo :
<script type="module" src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0-rc.5/vue.esm-browser.js"></script>
<div id="app">
<input type="checkbox" v-model="data"/>
</div>
<script type="module">
import {ref, createApp, onBeforeMount } from 'https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.0-rc.5/vue.esm-browser.js';
const app = createApp({
setup() {
let data = ref(false)
onBeforeMount(() => {
// data = true ❌
data.value = true // ✅
})
return { data };
}
});
app.mount('#app')
</script>

I do not think it is the checkbox, but that you should use the .value when you write to a ref. Otherwise you loose the reactivity and v-model will not work.
onBeforeMount(async () => {
data.value = await fetchingData()
})
Hope this helps.

Related

Can I use watch from Vue 3 with primitives?

Every time I fetch data, I want to change boolean value to render <Loading /> component.
I don't want my condition to be dependant on array length. So I decided to do it this way.
And <Loading /> component never reacts to state.isLoading change.
I tried to test whether this.isLoading changes at all using watch. But watch never logged anything.
I've never seen anybody using watch with primitives.
The problem is that I don't know if I can use watch with primitives and what I can use instead, like useEffect in React.
App.vue
<script setup>
import { RouterView } from 'vue-router'
import { watch, ref, onMounted, reactive } from 'vue';
import Navbar from './components/Navbar.vue'
import { useShopStore } from './stores/shopStore';
const shop = useShopStore()
const bool = ref(shop.isLoading)
console.log(bool)
watch(bool.value, (newBool) => {
console.log(newBool)
}, { deep: true })
</script>
Category.vue
<template>
<LoadingVue v-if="shop.isLoading" />
<div v-else class="category__menu">
<CardVue
v-for="item in shop.category"
:item="item"
:key="item.id"
/>
</div>
</template>
ShopStore.js
actions: {
async getProducts(path) {
if (typeof path !== 'string' || path === undefined) return
this.setLoading()
try {
const response = fetch(`https://fakestoreapi.com/products/category/${path}`)
.then(res => res.json())
.then(res => this.category = res)
} catch (error) {
console.log(error)
alert('Something went wrong')
}
this.setLoading()
},
setLoading() {
console.log('setLoading')
this.isLoading = !this.isLoading
}
}
You are creating a new ref over a reactive data. It's like copying by value, the original reactive data and the new ref wrapped over it are not connected. So when shop.isLoading changes, your bool ref doesn't, they are two different variables now.
I guess you are using pinia for the store. If so, the shop.isLoading is already reactive, you don't have to wrap it into a ref.
<Loading v-model="shop.isLoading" />
You can also use storeToRefs helper method from pinia to use destructuring over your store and get refs of your state:
const { isLoading } = storeToRefs(shop)
console.log(isLoading.value)
So.
The problem was that I used async but I didn't use await inside the function and that's why condition worked the way it worked. Or didn't work as I expected.
Now I fixed it and I want to publicly admit that I am a complete moron.
Thank you for your attention.
P.S.
Still didn't figure out how to use watch. The only way is to watch the whole state object. watch doesn't react to only state.bool value change.

Svelte: Reactive computed variable bind to input

I have a reactive computed variable that is dependant to svelte store and gets initialized by making an API call when the page refreshes.
I want to bind this value to an input. With this code my input doesn't work (nothing can be typed in it)
Please see this REPL and here is the code:
This is App.svelte
<script>
import {loggedInUserProfile} from './store.js'
import { onMount } from 'svelte'
import {update} from './util.js'
let loggedInUserInfo
loggedInUserProfile.subscribe((value) => (loggedInUserInfo = value))
onMount(() => {
console.log('App onMount called')
update()
})
const capitalizeFirstLetter = (string) => {
return string?.charAt(0).toUpperCase() + string?.slice(1);
}
$: name = loggedInUserInfo?.name
$: lastName = loggedInUserInfo?.lastName
</script>
<div style="display:flex; flex-direction: column;">
<div>
<span>Name: </span><input label="name" bind:value={name}>
</div>
<div>
<span>Last Name: </span><input bind:value={lastName}>
</div>
</div>
And this is update in util mimicking an API call:
export const update = () => {
setTimeout(() => {
loggedInUserProfile.set({name: 'updated name', lastName: 'updated last name'})
}, 1000)
}
If I change the $ to let, the input will work, but I cannot have the updated value in the input. What is the solution here?
You should not use subscribe like that. For every manual subscribe you should call the returned function to unsubscribe. If you just want to get the value once outside a Svelte component, use get which can be imported from 'svelte/store'.
Just bind directly to the store value via $ syntax. You do not need any of the other script stuff. Using it like that the binding works both ways.
<input bind:value={$loggedInUserProfile.name} />
<input bind:value={$loggedInUserProfile.lastName} />

Using tiptap with v-model and <script setup> in Vue 3

I'm trying to use tiptap with Vue.js with the <script setup> approach of creating a Single File Component (SFC).
TextEditor.vue
<template>
<editor-content :editor="editor" class="editor" />
</template>
<script lang="ts" setup>
import { useEditor, EditorContent } from '#tiptap/vue-3'
import StarterKit from '#tiptap/starter-kit'
const props = defineProps({
modelValue: {
type: String,
default: "",
}
})
const emit = defineEmits(['update:modelValue'])
const editor = useEditor({
content: props.modelValue,
extensions: [StarterKit],
onUpdate: ({editor}) => {
let content = editor.getHTML()
emit('update:modelValue', content)
}
})
</script>
I then use this component like this:
<template>
<text-editor v-model="myModel.content" />
</template>
This works when <text-editor> is loaded after myModel.content is defined.
However, if <text-editor> loads before myModel.content is set from my database API, then the text content remains blank. From what I understand from looking at the examples in the tiptap docs, I need to somehow use watch to update my editor when props.modelValue is changed using something like this:
watch(() => props.modelValue, (newValue, oldValue) => {
const isSame = newValue === oldValue
console.log(`Same: ${isSame}`)
if (isSame) {
return
}
editor.commands.setContent(newValue, false)
})
However, in the snippet above, editor is a ShallowRef type and doesn't have a reference to commands to call setContent.
What is the best way to get the above example to work when loading tiptap with the <script setup> approach?
You need to access the ref actual value with .value
editor.value?.commands.setContent('<p>test</p>', false)

Nuxt3: how two chain two fetches?

I am trying to chain two fetch in Nuxt3, with the second one calling an URL based on the result of the first one and the resulting "variable" to be used in the Vue component.
I'm trying with
<script setup>
const url = "myurl";
const { data } = await useFetch(url);
watch(data, async () => {
// do something with the result
url2 = data.value.result
const variable = await useFetch(url2);
});
</script>
but it looks like the block inside the watch is not able to modify the values of the variables at all (e.g. if I define it outside and try to update it inside, even with hard coded values)
Am I missing something very obvious here?
Something like this works perfectly fine, since all the fetching will be awaited
<script setup>
const { data } = await useFetch('https://jsonplaceholder.typicode.com/todos/1')
console.log('data', data.value.userId)
const { data: photos } = await useFetch(`https://jsonplaceholder.typicode.com/photos/${data.value.userId}`)
console.log('data2', photos.value)
</script>
<template>
<div>
first data: {{ data }}
</div>
<hr />
<div>
photos: {{ photos }}
</div>
</template>

Vue3 Composition API - How to load default values from Ajax?

I have read everything I can find, but there is a confusing amount of variability between approaches. I want to use the "setup" form of the Vue3 composition API, which I believe is the recommended approach for future compatibility.
I have a form with elements like this:
<form #submit.prevent="update">
<div class="grid grid-cols-1 gap-6 mt-4 sm:grid-cols-2">
<div>
<label class="text-gray-700" for="accountID">ID</label>
<input disabled id="accountID" v-model="accountID"
class="bg-slate-100 cursor-not-allowed w-full mt-2 border-gray-200 rounded-md focus:border-indigo-600 focus:ring focus:ring-opacity-40 focus:ring-indigo-500"
type="text"
/>
</div>
I want to load the current values with Ajax. If the user submits the form then I want to save the changed fields with a PATCH request.
I cannot work out how to change the form value with the result of the Ajax request and still maintain the binding.
Vue3 blocks changing the props directly (which makes sense), so the code below does not work:
<script setup lang="ts">
import { ref, onMounted, computed } from "vue";
import axios from "axios";
import { useUserStore } from "#/stores/userStore";
const userStore = useUserStore();
const props = defineProps({
accountID: String,
});
const emit = defineEmits(['update:accountID'])
const accountID = computed({
get() {
return props.accountID;
},
set (value) {
return emit('update:accountID')
},
})
onMounted(async () => {
let response = await axios.get("http://localhost:8010/accounts", { headers: { "Authorization": "Bearer " + userStore.jws } });
// This is a readonly variable and cannot be reassigned
props.accountID = response.data.ID;
});
function update() {
console.log("Form submitted")
}
</script>
How can I set the form value with the result of the Ajax request?
Instead of trying to assign props.accountID, update the accountID computed prop, which updates the corresponding v-model:accountID via the computed setter. That v-model update is then reflected back to the component through the binding:
onMounted(async () => {
let response = await axios.get(…)
// props.accountID = response.data.ID ❌ cannot update readonly prop
accountID.value = response.data.ID ✅
})
Also note that your computed setter needs to emit the new value:
const accountID = computed({
get() {
return props.accountID
},
set(value) {
// return emit('update:accountID') ❌ missing value
return emit('update:accountID', value) ✅
},
})
demo