this.$forceUpdate equivalent in Vue 3 - Composition API? - vue.js

In Vue 2, instance method this.$forceUpdate() could be used to update the component manually. How can we force update component in Vue 3 - Composition API (inside setup method) ?
setup(props, context) {
const doSomething = () => {
/* how to call $forceUpdate here? */
}
return {
doSomething
}
}
Thanks, in advance.

If using Options API:
<script lang="ts">
import {getCurrentInstance, defineComponent} from 'vue'
export default defineComponent({
setup() {
const instance = getCurrentInstance();
instance?.proxy?.$forceUpdate();
}
})
</script>
If using Composition API with <script setup>
<script setup lang="ts">
import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance();
instance?.proxy?.$forceUpdate();
</script>

When I need to force an update in vue I usually add a key with a value I can change, which will then force vue to update it. That should work in vue 3 as well, though I admit I haven't ever tried it. Here's an example:
<template>
<ComponentToUpdate
:key="updateKey"
/>
</template>
<script>
export default {
data() {
return {
updateKey: 0,
};
},
methods: {
forceUpdate() {
this.updateKey += 1;
}
}
}
</script>
You can read more about it here: https://michaelnthiessen.com/key-changing-technique/

$forceUpdate is still available in Vue3, but you won't have access to it in the setup() function. If you absolutely need to use it, you can use object API component or this fancy trick...
app.component("my-component", {
template: `...`,
methods: {
forceUpdate(){
this.$forceUpdate()
}
},
setup(props) {
const instance = Vue.getCurrentInstance();
Vue.onBeforeMount(()=>{
// instance.ctx is not populated immediately
instance.ctx.forceUpdate();
})
return {doSomething};
},
})
If this seems like a ridiculous solution, trust your Judgement. Ideally your application would not rely on forceUpdate. If you are relying on it, it likely means that something is miss-configured, and that should be the first thing to resolve.

Related

Why is this composable not reactive?

I have this composable in my app:
// src/composition-api/usePermissions.js
import { ref, readonly } from 'vue'
import { fetchData } from 'src/utils/functions/APIFunctions'
export function usePermissions() {
const permissions = ref([])
const name = ref('')
const fetchCurrentUser = () => {
fetchData('users/me').then(res => {
name.value = `${res.first_name} ${res.last_name}`
permissions.value = res.roles
})
}
return {
name: readonly(name),
permissions: readonly(permissions),
fetchCurrentUser,
}
}
FetchCurrentUser is called in the main layout component.
<template>
<!-- src/layouts/MainLayout.vue -->
<q-layout view="lHh Lpr lFf">
<!-- Redacted for brevity -->
</q-layout>
</template>
<script setup>
import { defineComponent, ref, onMounted } from "vue";
import EssentialLink from "src/components/EssentialLink.vue";
import { usePermissions } from "src/composition-api/usePermissions";
const { fetchCurrentUser } = usePermissions();
/* redacted for brevity */
onMounted(() => {
fetchCurrentUser();
});
</script>
And the state is used in other components, such as this one.
<template>
<!-- src/components/loggedUserLabel.vue -->
<div>{{ name }}</div>
</template>
<script setup>
import { usePermissions } from "src/composition-api/usePermissions.js";
const { name } = usePermissions();
</script>
The fetchCurrentUser() function is used when starting the app or when a new user logs in. I want to use this composable in other components to restrict access to some parts of the app depending on user permissions, and to display the username. However, the name and permissions properties are not reacting to changes. What could be wrong here?
If it matters, I'm using Quasar. I could use Pinia for this but I only need this kind of store-like shared state here, so it seems like overkill to add another library.
At Estus Flask's request, I have created a MCVE - and the same problem keeps happening. Note how in src/layouts/MainLayout.vue the API call is made, but the message in src/components/PokemonLabel.vue does not update, despite receiving a valid response from the Pokeapi.
I have found a similar answered question but it is about a different situation.
Thanks in advance for your help!
name and fetchCurrentUser are supposed to be used in the same component. The state of fetchCurrentUser isn't shared between components, this is by design.
In order to make the state global, it should be created once per component hierarchy, e.g.:
const name = ref('')
export function usePermissions() {
const fetchCurrentUser = ...
return {
name: readonly(name),
fetchCurrentUser,
}
}
This won't work correctly for SSR application like Quasar because there are multiple application instances for different users, but the state is created once and shared between them. In this case the state likely should be created for a hierarchy of components, e.g.:
export function setupPermissions() {
const name = ref('')
const fetchCurrentUser = ...
provide('permissionsStore', {
name: readonly(name),
fetchCurrentUser,
})
}
export function usePermissions() {
return inject('permissionsStore');
}
Where usePermissions is used in child component. And setupPermissions is used in root component, or can be rewritten as a plugin.

Is there a way to watch a change of the i18n's current locale?

How can I call a function when the language/locale changes while using i18n?
Should I use Vuex or does vue-i18n has a method that allows me to do that?
You could watch $i18n.locale for any changes but usually, you do change this kind of thing. What is your use-case for such need ?
watch: {
'$i18n.locale': function(newVal, oldVal) {
console.log('locale change', newVal)
}
}
Not sure about your setup, but with Vue3 in SFC I am using it as follows:
New Way:
(changed according to #kissu's tip)
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
const { t, locale } = useI18n();
watch(locale, () => {
console.log('locale', locale.value);
});
</script>
It fires every time the locale changes (for example with a language toggle outside of the actual file/component).
Old Way:
<script setup lang="ts">
import i18n from '#/i18n';
const locale = ref(i18n.global.locale);
watch(locale, () => {
console.log('locale', locale.value);
});
</script>

Watch child properties from parent component in vue 3

I'm wondering how I can observe child properties from the parent component in Vue 3 using the composition api (I'm working with the experimental script setup).
<template>//Child.vue
<button
#click="count++"
v-text="'count: ' + count"
/>
</template>
<script setup>
import { ref } from 'vue'
let count = ref(1)
</script>
<template>//Parent.vue
<p>parent: {{ count }}</p> //update me with a watcher
<Child ref="childComponent" />
</template>
<script setup>
import Child from './Child.vue'
import { onMounted, ref, watch } from 'vue'
const childComponent = ref(null)
let count = ref(0)
onMounted(() => {
watch(childComponent.count.value, (newVal, oldVal) => {
console.log(newVal, oldVal);
count.value = newVal
})
})
</script>
I want to understand how I can watch changes in the child component from the parent component. My not working solution is inspired by the Vue.js 2 Solution asked here. So I don't want to emit the count.value but just watch for changes.
Thank you!
The Bindings inside of <script setup> are "closed by default" as you can see here.
However you can explicitly expose certain refs.
For that you use useContext().expose({ ref1,ref2,ref3 })
So simply add this to Child.vue:
import { useContext } from 'vue'
useContext().expose({ count })
and then change the Watcher in Parent.vue to:
watch(() => childComponent.value.count, (newVal, oldVal) => {
console.log(newVal, oldVal);
count.value = newVal
})
And it works!
I've answered the Vue 2 Solution
and it works perfectly fine with Vue 3 if you don't use script setup or explicitly expose properties.
Here is the working code.
Child.vue
<template>
<button #click="count++">Increase</button>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
return {
count: ref(0),
};
},
};
</script>
Parent.vue
<template>
<div id="app">
<Child ref="childComponent" />
</div>
</template>
<script>
import { ref, onMounted, watch } from 'vue';
import Child from './components/Child.vue';
export default {
components: {
Child,
},
setup() {
const childComponent = ref(null);
onMounted(() => {
watch(
() => childComponent.value.count,
(newVal) => {
console.log({ newVal }) // runs when count changes
}
);
});
return { childComponent };
},
};
</script>
See it live on StackBlitz
Please keep reading
In the Vue 2 Solution I have described that we should use the mounted hook in order to be able to watch child properties.
In Vue 3 however, that's no longer an issue/limitation since the watcher has additional options like flush: 'post' which ensures that the element has been rendered.
Make sure to read the Docs: Watching Template Refs
When using script setup, the public instance of the component it's not exposed and thus, the Vue 2 solutions will not work.
In order to make it work you need to explicitly expose properties:
With script setup
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
With Options API
export default {
expose: ['publicData', 'publicMethod'],
data() {
return {
publicData: 'foo',
privateData: 'bar'
}
},
methods: {
publicMethod() {
/* ... */
},
privateMethod() {
/* ... */
}
}
}
Note: If you define expose in Options API then only those properties will be exposed. The rest will not be accessible from template refs or $parent chains.

vue 3 directives listen vue emit

What i want to achieve is to build a loading vue directives,
function with v-loading directive will render spinner and block event till function promise resolve or reject.
What i had tried so far:
use addEventListener, but it can only listen dom's native event, not vue event
hijack vue $emit function, but get a warning said that don't override vue native function named $, even if this solution work, i think this is a bad solution.
in directives argument, binding.instance[binding.value.name] refer to onEvent function in component, i tried override it but it doesn't work. When onEvent trigger again, it run old onEvent before override.
third party event emitter(eg, mitt). this method works well, but custom-component have to write extra code to emit event.
As example code below,
user of v-loading have to remember to write 2 emit (mitt and vue's emit).
It is not that straight forward, and it has extra dependency.
// mitt solution
// custom-component template
<custom-component v-loading:event="onEvent">
// custom-component script
setup(props, {emit}) {
function emitEvent() {
emit("event");
// bad: have to remember to write this extra line, and it is third party dependency
mittEmitter.emit("event");
}
}
So, any other solution to listen vue's event(not dom's native event) from vue's $emit?
LoadingDirective.ts
import { Directive } from "vue";
const Loading: Directive = {
mounted(el, binding) {
const eventName = binding.arg;
const onEvent = binding.value;
// I want to listen vue's event(eventName) here
// do something extra
onEvent(); // original onEvent() function to run in App.vue
// do something extra
}
};
export default Loading;
App.vue
<template>
<!-- when onEvent triggered, a spinner will be render in custom-component -->
<custom-component v-loading:event="onEvent" />
</template>
CustomComponent.vue
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
setup(props, {emit}) {
function emitEvent() {
// use only vue's emit
emit("event")
}
return {
onEvent
};
}
});
</script>
The Vue 3 documentation recommends using an external library such as mitt or tiny-emitter.
JSFiddle Example
<template>
<div id="app">
<custom-component v-loading="eventHandler" />
</div>
</template>
const emitter = mitt();
const customComponent = { template: '<h1>Example</h1>' };
const app = Vue.createApp({
components: { customComponent },
setup() {
setTimeout(() => {
emitter.emit('loadingEvent', { colour: 'Green' });
}, 1000);
const eventHandler = e => console.log('Handled!', e);
return { eventHandler };
},
});
app.directive('loading', {
mounted(el, binding) {
const func = binding.value;
emitter.on('loadingEvent', data => {
// your logic here...
func(data);
});
},
});
app.mount('#app');

How to access the window object in vue js?

I have this vue js component:
<template>
<div>
hello world
</div>
</template>
<script>
export default {
name: 'mycomp',
data: function () {
console.error("window.google_recaptcha_public_key", window.google_recaptcha_public_key);
return {
}
},
mounted() {
let app = this;
console.error("window.google_recaptcha_public_key2", window.google_recaptcha_public_key);
},
}
</script>
<style scoped lang="scss">
</style>
returns:
window.google_recaptcha_public_key undefined
window.google_recaptcha_public_key2 undefined
where can I leave painless and happy all global configuration?
notice this configuration lives in my laravel backend. So I wont copy paste all values from the backend to the front end
U can use Vue.prototype in main.js file, or in file you import Vue
Vue.prototype.Hereanyname = window.hereanyname;
and in your Vue application, you can use it
Hereanyname.thefunction
Real example on Laravel
in main.js
import Vue from 'vue';
Vue.prototype.Routes = window.routes;
new Vue({
el: '#app',
template: '<App/>',
components: {App}
});
in your application
:href="Routes.route('laravel.route.here')"
So for your case
import Vue from 'vue';
Vue.prototype.GoogleRecaptcha = window.google_recaptcha_public_key;
new Vue({
el: '#app',
template: '<App/>',
components: {App}
});
inside application
mounted() {
console.log(this.GoogleRecaptcha)
}
In Vue3, you no longer have the global Vue instance, so you need to assign the window as a global property on your app...
// main.js
app.config.globalProperties.window = window
Then in your components, window will just work.
This info is from an impeccable source.
You should save your window variable in Vue.prototype
main.js
Vue.prototype.$authUser = window.authUser;
After that, you can access your data as follows:
Vue template
<div v-text="$authUser.name"></div>
Vue script
let name = this.$authUser.name;
window is available in the vuex store. This may help if you need to mutate the window property synchronously with other actions/mutations, give you a chance to validate what goes into it, or catch an error if the variable you intend to put there isn't available.
export default new Vuex.store({
state: {
windowFoo: window.foo,
googleRecaptcha: window.google_recaptcha_public_key
},
getters: {
windowFoo: (state) => state.windowFoo,
googleRecaptcha: (state) => state.googleRecaptcha
},
actions: {
barThenFooThenBaz({ commit }, { foo }) {
// ... do some other required actions first
commit("setWindowFoo", foo);
// ... do some dependent actions next
}
},
mutations: {
setWindowFoo(state, foo) {
state.windowFoo = foo;
}
}
});
Then from your Single File Component...
//....
computed: {
windowFoo() {
return this.$store.getters.windowFoo;
},
googleRecaptcha() {
return this.$store.getters.googleRecaptcha;
}
},
methods: {
async barThenFooThenBaz(foo) {
await this.$store.dispatch({
type: "barThenFooThenBaz",
foo: foo
});
// ... do something dependent on windowFoo being set
}
}
//....
Although the other answers here are totally acceptable, I've had issues using the Vue instance with Vue.prototype in main.js as our project has gotten larger, so I hope this helps!
Provide/Inject works nicely. Here's an example with Vue 3:
main.js
const app = createApp(App)
app.provide('recaptcha_key', window.google_recaptcha_public_key)
app.mount('#app')
MyComponent.vue
<script setup>
const { inject } from 'vue'
const recaptchaKey = inject('recaptcha_key')
</script>