I'm migrating my code from vue 2 to vu 3. I want to make the contents of a textarea on the autofocus, but what happens is an error message Uncaught (in promise) TypeError: content.value.focus is not a function. Here is my code:
<textarea
ref="content"
cols="30"
rows="10"
class="input"
placeholder="Paste your HTML here"
v-model="content">
</textarea>
<script setup>
import { nextTick, onMounted, ref } from 'vue';
const modalShown = ref(false);
let existingBlot = ref(null);
const content = ref('');
const applyHTML = () => {
emit('adding', {
content: content,
existingBlot: existingBlot,
});
close();
}
const close = () => {
modalShown.value = false;
content.value = '';
existingBlot = null;
}
onMounted(() => {
emitter.on('openingHTMLEmbedder', data => {
if (data) {
content = data.content;
existingBlot = data.existingBlot;
}
nextTick(() => { content.value.focus() })
modalShown.value = true;
});
});
</script>
i don't have any idea where is the mistake, or how to solve this.
so, I'm glad that anyone here can help me by giving me some suggestions to solve this problem.
Thank you in advance
you can try
setTimeout(()=>{content.value.focus()},0)
Related
I'm trying to develop microfrontend with Vuejs and Module Federation. So, i have method loadModule, that returns me a an object(maybe component), but what i should do with it i don't know. I tried to import this component like defineAsyncComponent but it's useless.
And i'm tried use the render function in this object and paste this it to markup.
The screenshot shows what the object I get, but I don’t know what to do next with it and how to register this like component.
I'm stumped, any advice would be welcome
<script setup>
import { ref, onMounted, defineAsyncComponent, h } from 'vue'
import TestComp from './TestComp.vue'
let Header
const modules = [
{
protocol: 'http',
host: 'localhost',
port: 8080,
moduleName: 'Company',
fileName: 'remoteEntry.js',
componentNames: ['Header'],
},
]
onMounted(() => {
modules.forEach((uiApplication) => {
const remoteURL = `${uiApplication.protocol}://${uiApplication.host}:${uiApplication.port}/${uiApplication.fileName}`
const { componentNames } = uiApplication
const { moduleName } = uiApplication
const element = document.createElement('script')
element.type = 'text/javascript'
element.async = true
element.src = remoteURL
element.onload = () => {
componentNames?.forEach((componentName) => {
const component = loadComponent(moduleName, `./${componentName}`)
component().then((res) => {
Header = res
console.log(res)
// what should i do next?
// This is not working
// Header = defineAsyncComponent(() => import('Company/Header'))
})
})
}
document.head.appendChild(element)
})
})[enter image description here][1]
function loadComponent(scope, module) {
return async () => {
// Initializes the shared scope. Fills it with known provided modules from this build and all remotes
await __webpack_init_sharing__('default')
const container = window[scope] // or get the container somewhere else
// Initialize the container, it may provide shared modules
await container.init(__webpack_share_scopes__.default)
const factory = await window[scope].get(module)
const Module = factory()
return Module
}
}
const remoteImport = async (location, name, options) => {
const module = await importResource(location, options)
if (name) {
let m = module[name]
if (!m) {
throw new Error(
`No component named ${name} founded in component ${location}`
)
}
return module[name]
} else {
return module
}
}
</script>
<template>
<div>
<Header ref="Header" title="Test shop" />
<h2>Our Shop Page</h2>
</div>
</template>
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 am building a project with Nuxt and I need to know the size of the wrapper to adjust the grid setting
(I want a single line, I could still do this in pure CSS probably by hiding the items)
It's my first time using composition API & script setup
<script setup>
const props = defineProps({
name: String
})
const width = ref(0)
const wrapper = ref(null)
const maxColumns = computed(() => {
if (width.value < 800) return 3
if (width.value < 1000) return 4
return 5
})
onMounted(() => {
width.value = wrapper.value.clientWidth
window.onresize = () => {
width.value = wrapper.value.clientWidth
console.log(width.value);
};
})
</script>
<template>
<div class="category-preview" ref="wrapper">
...
</div>
</template>
The console log is working properly, resizing the window and refreshing the page will return 3, 4 or 5 depending on the size, but resizing won't trigger the computed value to change
What am I missing ?
In my test enviroment I had to rename your ref 'width' into something else. After that it did worked for me with a different approach using an event listener for resize events.
You can do something like this:
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue'
const wrapperWidth = ref(0)
const wrapper = ref(null)
// init component
onMounted(() => {
getDimensions()
window.addEventListener('resize', debounce(() => getDimensions(), 250))
})
// remove event listener after destroying the component
onUnmounted(() => {
window.removeEventListener('resize', debounce)
})
// your computed property
const maxColumns = computed(() => {
if (wrapperWidth.value < 800) {
return 3
} else if (wrapperWidth.value < 1000) {
return 4
} else {
return 5
}
})
// get template ref dimensions
function getDimensions () {
const { width } = wrapper.value.getBoundingClientRect()
wrapperWidth.value = width
}
// wait to call getDimensions()
// it's just a function I have found on the web...
// there is no need to call getDimensions() after every pixel have changed
const debounce = (func, wait) => {
let timeout
return function executedFunction (...args) {
const later = () => {
timeout = null
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
</script>
<template>
<div ref="wrapper">
{{ maxColumns }} // will change after resize events
</div>
</template>
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 // ✅