in Following, What i want is, when i change status code using Radio button to get data according to status like "Active, Draft or Trash" from API. CONST params should update.
but on changing the radio input, params is not getting updated. it always print
api_key=**********&language=en-US&deletedStatus=false&byCity=&byCountry=&page=1&ActiveStatus=true&limit=20
but in vue dev tools, i can see these individual datas are updated except params.
<template>
<div
v-for="(status, index) in StatusOptions"
:key="index"
class="field-radiobutton ttc-pr-2"
>
<RadioButton
:id="status.value"
name="status"
:value="status.value"
#change="ChangeState(status.value)"
/>
<label>{{ status.name }}</label>
</div>
</template>
<script setup>
import { ref, reactive } from "vue";
const { apiUrl } = useRuntimeConfig();
const router = useRouter();
const route = useRoute();
const deletedStatus = ref(false);
const byCity = ref("");
const byCountry = ref("");
const page = ref(1);
const ActiveStatus = ref(true);
const limit = ref(20);
const StatusOptions = ref([
{ name: "Active", value: "Active" },
{ name: "Draft", value: "Draft" },
{ name: "Trash", value: "Trash" },
]);
const ChangeState = (state) => {
if (state == `Active`) {
deletedStatus.value = false;
ActiveStatus.value = true;
}
if (state == `Draft`) {
ActiveStatus.value = false;
}
if (state == `Trash`) {
ActiveStatus.value = false;
deletedStatus.value = true;
}
console.log(params.value)
FetchTours();
};
const params = ref(
[
"api_key=****************",
"language=en-US",
`deletedStatus=${deletedStatus.value}`,
`byCity=${byCity.value}`,
`byCountry=${byCountry.value}`,
`page=${page.value}`,
`ActiveStatus=${ActiveStatus.value}`,
`limit=${limit.value}`,
].join("&")
);
const FetchTours = async () => {
const { data } = await useFetch(`${apiUrl}/tours/gettours?${params.value}`, {
key: "someKey",
initialCache: false,
});
AllTours.value = data.value;
};
</script>
I think what you are looking for is a computed value. The refs is oly initiated with the base information. Passing .value to your params variable only assigns the value, not the reference.
The usage of computed will track the dependencies of this variable, and will update accordingly when one of the dependencies changes.
const params = computed(() => [
"api_key=****************",
"language=en-US",
`deletedStatus=${deletedStatus.value}`,
`byCity=${byCity.value}`,
`byCountry=${byCountry.value}`,
`page=${page.value}`,
`ActiveStatus=${ActiveStatus.value}`,
`limit=${limit.value}`,
].join("&")
);
Related
I am upgrading an app from vue 2 to vue 3 and I am having some issues with composables. I'd like to use props in the composable but it doesn't seem to be working. The code sample is pulled from a working component and works fine when I leave it in the component.
I assume defineProps isn't supported by composables, but then I am unclear how to handle it. When I pass the src in the parameters it loses its reactivity.
// loadImage.js
import { defineProps, onMounted, ref, watch } from 'vue'
// by convention, composable function names start with "use"
export function useLoadImage() {
let loadingImage = ref(true)
let showImage = ref(false)
const props = defineProps({
src: String,
})
const delayShowImage = () => {
setTimeout(() => {
showImage.value = true
}, 100)
}
const loadImage = (src) => {
let img = new Image()
img.onload = (e) => {
loading.value = false
img.onload = undefined
img.src = undefined
img = undefined
delayShowImage()
}
img.src = src
}
onMounted(() => {
if (props.src) {
loadImage(props.src)
}
})
watch(
() => props.src,
(val) => {
if (val) {
loadingImage.value = true
loadImage(val)
}
},
)
// expose managed state as return value
return { loadingImage, showImage }
}
Edit
This method worked for me, but the two methods mentioned in the comments below did not.
I have a new question here.
// loadImage.js
import { onMounted, ref, watch } from 'vue'
// by convention, composable function names start with "use"
export function useLoadImage(props) {
let loadingImage = ref(true)
let showImage = ref(false)
const delayShowImage = () => {
setTimeout(() => {
showImage.value = true
}, 100)
}
const loadImage = (src) => {
let img = new Image()
img.onload = (e) => {
loading.value = false
img.onload = undefined
img.src = undefined
img = undefined
delayShowImage()
}
img.src = src
}
onMounted(() => {
if (props.src) {
loadImage(props.src)
}
})
watch(
() => props.src,
(val) => {
if (val) {
loadingImage.value = true
loadImage(val)
}
},
)
// expose managed state as return value
return { loadingImage, showImage }
}
<script setup>
import { defineProps, toRef } from 'vue'
import { useLoadImage } from '../../composables/loadImage'
const props = defineProps({
src: String
})
const { loading, show } = useLoadImage(props)
</script>
According to official docs :
defineProps and defineEmits are compiler macros only usable inside <script setup>
You should pass the props as parameter without destructing them to not loose the reactivity :
export function useLoadImage(props) {
....
}
you can use useRef to pass specific props without losing reactivity
const imgRef = toRef(props, "img");
const { loding, show } = useLoadImage(imgRef);
I have a design in setting page,every one of them hava reset button, now i using pinia to be store library.
I kown $reset is reset the whole pinia state,so,how to reset one of data in pinia state?
The typical way I do this:
const defaultState = {
foo: 'bar'
}
export const useFoo = defineStore('foo', {
state: () => ({ ...defaultState }),
actions: {
reset() {
Object.assign(this, defaultState);
}
}
})
You get the initial state and a reset() action which resets whatever state has to the initial. Obviously, you can pick and choose what you put in defaultState.
If you only want to reset one particular state prop, without touching anything else, just assign the default value to it:
useFoo().foo = 'bar';
If you find it useful, you can also have a generic update, where you can assign multiple values to state in one call:
actions: {
update(payload) {
Object.assign(this, payload)
}
}
Use it like:
useFoo().update({
foo: 'bar',
// add more props if needed...
});
Last, but not least, lodash's pick can be used to pick and choose what gets reset, from the defaultState values, without having to specify the actual values:
import { pick } from 'lodash-es';
const defaultState = {
foo: 'bar',
boo: 'far'
};
export const useFoo = defineStore('foo', {
state: () => ({ ...defaultState }),
actions: {
reset(keys) {
Object.assign(this, keys?.length
? pick(defaultState, keys)
: defaultState // if no keys provided, reset all
);
}
}
})
use it like:
useFoo().reset(['foo']);
This only resets foo to 'bar', but doesn't touch current value of boo.
To reset both (using the action above):
useFoo().reset(['foo', 'boo']);
...or useFoo().reset() or useFoo().reset([]), both of which reset all the state, because the keys?.length condition is falsey.
Here's a working example:
const { createPinia, defineStore, storeToRefs } = Pinia;
const { createApp, reactive, toRefs } = Vue;
const defaultState = {
foo: "bar",
boo: "far",
};
const useStore = defineStore("foobar", {
state: () => ({ ...defaultState }),
actions: {
reset(keys) {
Object.assign(
this,
keys?.length ? _.pick(defaultState, keys) : defaultState
);
},
},
});
const pinia = createPinia();
const app = createApp({
setup() {
const store = useStore();
const localState = reactive({
resetFoo: false,
resetBoo: false,
});
const resetStore = () => store.reset(
[
localState.resetFoo ? "foo" : null,
localState.resetBoo ? "boo" : null,
].filter((o) => o)
);
return { ...storeToRefs(store), ...toRefs(localState), resetStore };
},
});
app.use(pinia);
app.mount("#app");
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/vue-demi"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pinia/2.0.28/pinia.iife.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<div id="app">
<input v-model="foo" />
<input v-model="boo" />
<pre v-text="JSON.stringify({foo, boo}, null, 2)"></pre>
<hr>
<label>
<input type="checkbox" v-model="resetFoo" />ResetFoo</label>
<label>
<input type="checkbox" v-model="resetBoo" />ResetBoo</label>
<button #click="resetStore">Reset</button>
</div>
Above example doesn't reset one property to the default value when the property is already changed.
That's because the defaultState is reactive, you need to copy the defaultState so it's not reactive anymore.
import _pick from 'lodash.pick';
const defaultState = {
foo: 'bar',
};
export const useStore = defineStore('store', {
state: () => ({...defaultState}),
actions: {
reset(keys) {
Object.assign(this, keys?.length
? _pick(defaultState, keys)
: defaultState // if no keys provided, reset all
);
}
}
})
Use it like this
useStore().reset(['foo']);
This will now reset foo back to bar
I'm using watch to track the input element value, which fires every time the value changes. Is there a way to call watch explicitly?
like:
click Event --> isDataChanged --> call watch --> returns true if ref changed
<template>
<input type="text" v-model="userInput" />
<button #click="isChanged">change</button>
<p></p>
</template>
<script>
import { watch, ref } from "#vue/runtime-core";
export default {
setup() {
const userInput = ref("");
const isChanged = ()=> {
// call watch function
console.log("changed")
}
watch([userInput], (newValues, prevValues) => {
if (newValues) {
console.log(newValues)
return true;
}
});
return {
userInput,
isChanged,
};
},
};
</script>
You could factor out the watcher into a separate function that isChanged() could call when needed:
const userInput = ref('');
let lastNewValue = null;
let lastPrevValue = null;
const onUserInputChanged = (newValues = lastNewValue, prevValues = lastPrevValue) => {
lastNewValue = newValues;
lastPrevValue = prevValues;
if (newValues !== prevValues) {
return true;
}
};
const isChanged = () => {
const changed = onUserInputChanged();
console.log(changed ? 'changed' : 'no change');
};
watch(userInput, onUserInputChanged);
demo
I am really scratching my head at this.
I am making a CRUD application, and this problem started when I was working on the Edit component.
I am getting the error Cannot read property 'id' of null
BUT! The interesting thing is that the data actually DOES get updated, both in the application and on the server side.
The error however affects the layout. First of all, the delete button appears two places in the template instead of one, and instead of redirecting me to the main page when I update, the main page appears like a new div on the edit page. I have no idea what is going on.
Here are the different components/composables:
The Details component: Here the information about a specific document is stored based on it's ID.
<template>
<div v-if="playlist" class="playlist-details">
<div class="playlist-info">
<div class="cover">
<img :src="playlist.coverUrl">
</div>
<h2> {{ playlist.title }}</h2>
<p> {{ playlist.description }} </p>
</div>
</div>
<button #click="handleDelete">Delete</button>
<EditSong :playlist="playlist" />
</template>
<script>
import EditSong from '../components/EditSong'
import useDocument from '../composables/useDocument'
import getDocument from '../composables/getDocument'
import useStorage from '../composables/useStorage'
import { useRouter } from "vue-router";
export default {
props: ['id'],
components: { EditSong },
setup(props) {
const { document: playlist } = getDocument('playlists', props.id)
const { deleteDoc } = useDocument('playlists', props.id)
const router = useRouter();
const { deleteImage } = useStorage()
const handleDelete = async () => {
await deleteImage(playlist.value.filePath)
await deleteDoc()
confirm('Do you wish to delete this content?')
router.push({ name: "Home" });
}
return {
playlist,
handleDelete
}
}
}
</script>
Here is the Edit component: This is where I edit and update the data inside the Details component. This is where I am getting the TypeError.
It has something to do with the props.playlist.id field
<template>
<div class="edit-song">
<form #submit.prevent="handleSubmit">
<input type="text" required placeholder="title" v-model="title">
<input type="text" required placeholder="description" v-model="description">
<button v-if="!isPending">Update</button>
<button v-else disabled>Updating...</button>
</form>
</div>
</template>
<script>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import useDocument from '../composables/useDocument'
import useCollection from '../composables/useCollection'
export default {
props: ['playlist'],
setup(props) {
const title = ref('')
const description = ref('')
const { updateDoc } = useDocument('playlists', props.playlist.id)
const { error } = useCollection()
const isPending = ref(false)
const router = useRouter();
const handleSubmit = async () => {
await updateDoc({
title: title.value,
description: description.value,
})
isPending.value = false
if(!error.value) {
router.push({ name: "Home" })
}
}
return {
title,
description,
handleSubmit,
isPending,
error
}
}
}
</script>
And last, this is the Update composable: that stores the update function
import { ref } from 'vue'
import { projectFirestore } from '../firebase/config'
const useDocument = (collection, id) => {
const error = ref(null)
const isPending = ref(false)
let docRef = projectFirestore.collection(collection).doc(id)
const updateDoc = async (updates) => {
isPending.value = true
error.value = null
try {
const res = await docRef.update(updates)
isPending.value = false
return res
}catch(err) {
console.log(err.message)
isPending.value = false
error.value = 'Could not update document'
}
}
return {
error,
isPending,
updateDoc
}
}
export default useDocument
The likely scenario is getDocument() returns a ref to null for document, which gets updated asynchronously:
const getDocument = (collection, id) => {
const document = ref(null)
someAsyncFunc(() => {
document.value = {...}
})
return {
document
}
}
Since the document (renamed to playlist) is bound to the EditSong component, it receives both the initial value (null) and then the asynchronously populated value, which leads to the behavior you're seeing.
One solution is to conditionally render EditSong on playlist:
<EditSong v-if="playlist" :playlist="playlist" />
Another is to move the updateDoc initialization into handleSubmit, and add a null-check there:
const handleSubmit = async () => {
if (!props.playlist) return
const { updateDoc } = useDocument('playlists', props.playlist.id)
await updateDoc(...)
}
In vuejs3 app
I read data with axios request from backend API. I see that data are passed to internal
component, but I do not see content of the child component is rendered on the page.
Parent component:
<template>
<div class="row m-0 p-0" v-show="forumCategories.length && isPageLoaded">
<div v-for="(nextActiveForumCategory, index) in forumCategories" :key="nextActiveForumCategory.id" class="col-sm-12 col-md-6 p-2 m-0">
index::{{ index}}
<forum-category-block
:currentLoggedUser="currentLoggedUser"
:nextActiveForumCategory="nextActiveForumCategory"
:index="index"
:is_show_location="true"
></forum-category-block>
</div>
</div>
</template>
<script>
import ForumCategoryBlock from '#/views/forum/ForumCategoryBlock.vue'
import { useStore } from 'vuex'
export default {
name: 'forumsByCategoryPage',
components: {
ForumCategoryBlock,
},
setup () {
const store = useStore()
const orderBy = ref('created_at')
const orderDirection = ref('desc')
const forumsPerPage = ref(20)
const currentPage = ref(1)
let forumsTotalCount = ref(0)
let forumCategories = ref([])
let isPageLoaded = ref(false)
let credentialsConfig = settingCredentialsConfig
const currentLoggedUserToken = computed(
() => {
return store.getters.token
}
)
const currentLoggedUser = computed(
() => {
return store.getters.user
}
)
const forumsByCategoryPageInit = async () => {
loadForums()
}
function loadForums() {
isPageLoaded = false
let credentials = getClone(credentialsConfig)
credentials.headers.Authorization = 'Bearer ' + currentLoggedUserToken.value
let filters = { current_page: currentPage.value, order_by: orderBy.value, order_direction: orderDirection.value }
const apiUrl = process.env.VUE_APP_API_URL
axios.get(apiUrl + '/forums-by-category', filters, credentials)
.then(({ data }) => {
console.log('/forums-by-category data::')
console.log(data)
forumCategories.value = data.forumCategories
forumsTotalCount.value = data.forumsTotalCount
isPageLoaded = true
console.log('++forumCategories::')
console.log(forumCategories)
})
.catch(error => {
console.error(error)
isPageLoaded = true
})
} // loadForums() {
onMounted(forumsByCategoryPageInit)
return {
currentPage, orderBy, orderDirection, isPageLoaded, loadForums, forumCategories, getHeaderIcon, pluralize, forumsTotalCount, forumCategoriesTitle, currentLoggedUser
}
} // setup
</script>
and ForumCategoryBlock.vue:
<template>
<div class="">
<h1>INSIDE</h1>
<fieldset class="bordered" >
<legend class="blocks">Block</legend>
nextActiveForumCategory::{{ nextActiveForumCategory}}<br>
currentLoggedUser::{{ currentLoggedUser}}<br>
index::{{ index }}<br>
</fieldset>
</div>
</template>
<script>
import { computed } from 'vue'
export default {
name: 'forumCategoryBlock',
props: {
currentLoggedUser: {
type: Object,
default: () => {}
},
nextActiveForumCategory: {
type: Object,
default: () => {}
},
index: {
type: Number,
default: () => {}
}
},
setup (props) {
console.log('setup props::')
console.log(props)
const nextActiveForumCategory = computed({
get: () => props.value.nextActiveForumCategory
})
const currentLoggedUser = computed({
get: () => props.value.currentLoggedUser
})
const index = computed({
get: () => props.index
})
return { /* currentLoggedUser, nextActiveForumCategory, index */ }
}
}
</script>
What I see in browser : https://prnt.sc/vh7db9
What is wrong abd how to fix it ?
MODIFIED :
I understood WHERE the error :
<div class="row m-0 p-0" v-show="forumCategories.length && isPageLoaded" style="border: 2px dotted red;">
if to remove 2nd condition && isPageLoaded in a line above I see content.
But looks like that var isPageLoaded is not reactive and I do not see why?
If is declared with ref and is declared in return of setup method.
But looks like as I modify it in loadForums method it does not work in template...
Thanks!
isPageLoaded is losing its reactivity because loadForums() is changing its type from ref to Boolean:
isPageLoaded = true // ❌ no longer a ref
isPageLoaded is a ref, so your code has to access it through its value property. It's probably best to use const instead of let here to avoid this mistake:
const isPageLoaded = ref(false)
isPageLoaded.value = true // ✅