Using script setup and reactive state vue 3 with toRefs - vue.js

I'm trying to use script setup in my vue project.
Before using script setup, my script would be like this:
<script>
import Layout from '../containers/Layout.vue';
import { reactive, toRefs } from 'vue'
export default {
name: 'Home',
setup() {
const state = reactive({});
return {
...toRefs(state),
};
},
components: { Layout, Layout }
}
</script>
Now I have it like this:
<script setup>
import Layout from '../containers/Layout.vue';
import { reactive, toRefs } from 'vue'
const state = reactive({});
const props = defineProps({
header: String
})
</script>
The thing that I am unsure about is how do I use the toRefs in this case? In the first case we are returning the variables so I understand the way we used ...toRefs(state)
But now, how do I use it? Or is it not needed anymore?
Thanks

script setup implicitly translate variable definitions
const a = ...
to
return {
a: ...
}
There is no substitute for return {...dynamicValue} in script setup, which is intended to suite common use cases only. This would require to combine it with script.
return {...toRefs(state)} serves no good purpose because the resulting refs aren't used in script block. Even if they are, they are usually defined as separate reactive values instead of state object:
const a = ref(...)
const b = reactive(...)
return { a, b }; // Not needed in script setup
If there is ever a need to handle these values as a single object, they could be combined together:
const a = ref(...)
const b = reactive(...)
const state = reactive({ a, b });
return { a, b }; // Not needed in script setup
This works it the same way for both script and script setup.

If you want to access the values of the state reactive directly in script setup you can use Object destructuring like this :
import { reactive, toRefs } from "vue"
const state = reactive({ name: "admin", age: 20 })
const { name, age } = toRefs(state)
Then you can access your values directly in the template
<template>
{{ name }}
</template>
However this is much less convenient to have to retype all of your properties

Related

How to fire an $emit event from Vue Composable

I have a vue composable that needs to fire an event. I naively set it up as follows:
*// composable.js*
import { defineEmits } from "vue";
export default function useComposable() {
// Vars
let buffer = [];
let lastKeyTime = Date.now();
const emit = defineEmits(["updateState"]);
document.addEventListener("keydown", (e) => {
// code
emit("updateState", data);
}
// *App.vue*
<template>
<uses-composables
v-show="wirtleState.newGame"
#updateState="initVars"
></uses-composables>
</template>
<script setup>
const initVars = (data) => {
//code here
}
// usesComposable.vue
<template>
<button #click="resetBoard" class="reset-button">Play Again</button>
</template>
<script setup>
import { defineEmits } from "vue";
import useEasterEgg from "#/components/modules/wirdle_helpers/useEasterEgg.js";
useEasterEgg();
</script>
The error I get is "Uncaught TypeError: emit is not a function useEasterEgg.js:30:11
So obviously you can not use defineEmits in a .js file. I dont see anywhere in Vue docs where they specifically use this scenario. I dont see any other way to do this but using $emits but that is invoked in a template which my composable does not have. Any enlightenment much appreciated.
You can emit events from a composable, but it will need to know where the events should be fired from using context which can be accessed from the second prop passed to the setup function: https://vuejs.org/api/composition-api-setup.html#setup-context
Composable:
export default function useComposable(context) {
context.emit("some-event")
}
Component script:
<script>
import useComposable from "./useComposable"
export default {
emits: ["some-event"],
setup(props, context) {
useComposable(context)
}
}
</script>
To use it in a script setup, the best way I found was to declare the defineEmit first, and assigning it to a const, and pass it as a param to your composable :
const emit = defineEmit(['example']
useMyComposable(emit);
function useMyComposable(emit){
emit('example')
}
You can't access emit this way, as the doc says : defineProps and defineEmits are compiler macros only usable inside script setup. https://vuejs.org/api/sfc-script-setup.html
I'm not entirely sure of what you are trying to achieve but you can use vue-use composable library to listen to key strokes https://vueuse.org/core/onkeystroke/
Lx4
This question is a bit confusing. What is <uses-composable>? A composable generally is plan js/ts, with no template. If it had a template, it would just be a component. Even if it was a component, which I mean you could turn it into if thats what you wanted, I don't see anything there that suggests that would be a good idea.
So I guess the question is, why would you want to emit an event? If I'm following what you want, you can just do:
// inside useEasterEgg.js
document.addEventListener('keydown', () => {
// other code
const data = 'test';
updateStateCallback(data);
});
function useEasterEgg() {
function onUpdateState(callback) {
updateStateCallback = callback;
}
return {
onUpdateState,
}
}
// Put this wherever you want to listen to the event
const { onUpdateState } = useEasterEgg();
onUpdateState(data => console.log(data));
https://jsfiddle.net/tdfu3em1/3/
Alternatively, if you just want access to data, why not make it a ref and just use it where you want:
const data = ref();
document.addEventListener('keydown', () => {
// other code
data.value = resultOfOtherCode;
});
function useEasterEgg() {
return {
data,
}
}
// Anywhere you want to use it
const { data } = useEasterEgg();

Destructure Reactive object in Vue script setup

I'm following the Vue 3 documents on how to move to using the <script setup> tags to simplify my component code.
One of the perks of using this setup is that you no longer have to use the export default boilerplate to explicitly return an object: anything declared on the top level scope will be automagically available in templates.
The issue I have is that in my app I have a very large object as my initial state, which in my normal Vue 3 app I can return and have automatically destructured, like this:
<script>
import { reactive, toRefs } from 'vue'
export default {
setup() {
const state = reactive({
foo: 1,
bar: 2,
// the rest of a very large object
})
return toRefs(state)
}
}
</script>
This saves me having to declare each and every item in the object as its own ref(), removing boilerplate.
My question is, how can I achieve the same auto-destructuring in the mode of Vue, where it only detects top level declarations? I want to be able to reference the keys of the object directly, without having to use state.foo or state.bar, but not have to explicitly declare every single one as a const in order to make it available in the
<script setup>
import { reactive, toRefs } from 'vue'
const state = reactive({
foo: 1,
bar: 2,
// the rest of a very large object
})
const { foo, bar, ? } = toRefs(state) // how do I destructure this dynamically?
</script>
You can destructure your object like you are doing and save the rest of the object keys and values with the spread operator.
<script setup>
import { reactive, toRefs } from 'vue'
const state = reactive({
foo: 1,
bar: 2,
test: 'test',
// the rest of a very large object
})
const { foo, bar, ...rest } = toRefs(state) // how do I destructure this dynamically?
</script>
Every key but foo and bar can be reached by accessing the rest variable. Like rest.test
If this isn't what you're after, I don't think what you're trying to do is possible.
See this post if my answer wasn't what you're looking for:
How to destructure into dynamically named variables in ES6?

Vue 3 composition API with updating refs

I am passing a ref value to a composable function, updateableSetting. This has an initial value, settingA, but can be updated by the user. When updated, us it possible to have useFeature run again and return an updated feature value?
export default defineComponent({
setup() {
const updateableSetting = ref('settingA')
const { feature } = useFeature(updateableSetting.value)
}
})
Sure. Use a computed property
import { ref, defineComponent, computed} from 'vue'
const useFeature = (initialRef) => {
const feature = computed(() => initialRef.value + ' - I am always up to date!')
return {
feature
}
}
export default defineComponent({
setup() {
const updateableSetting = ref('settingA')
//make sure you don't pass .value here - pass the whole ref object instead
const { feature } = useFeature(updateableSetting)
return { feature }
}
})
If you mean "run a function everytime updatedValue changes", you can use
watch(
[updatedValue],
() => {console.log('updatedValue changed! doing some calculations...')}
)
I had same problem when using composables in nuxt 3.
In nuxt 3 new reactive state is available called useState. try useState instead of ref.
for more information see nuxt 3 document: useState nuxt3

Vue composition api not working, data not set with the extracted data from the dabase

Trying out the Vue 3 composition API to write some better code but I cant get it to work as I wanted to work. I cant get the values to update with the values from the DB.
// component part
<template>
<SomeChildComponent :value="settings"/>
</template>
// script part
<script>
import { ref, onMounted} from 'vue'
export default {
setup() {
let settings = ref({
active : 1,
update : 0,
...
})
// this wont change the values
const getSettingsValues = async () => {
const response = await axios.get('/api/settings')// works
settings.active.value = response.data.active;//undefined
settings.update.value = 1;//undefined (even with hardcoded value)
[and more]
}
getSettingsValues()
return { settings };
}
}
</script>
You're misplacing the field value when you use the ref property, it should be :
settings.value.active= response.data.active;
settings.value.update= 1

How to use shorter path to get vuex contents?

In my vuex /store/state.js I have an export default that looks like this:
export default {
foo: {
bar:{
tar: {
info: 42
}
}
}
}
So, whenever I want to access info, I usually do in my methods like this;
methods: {
getInfo () {
return this.$store.state.foo.bar.tar.info
}
}
This is just for a demo purpose, and mine is actually a bit worse, but I ended up doing the same so, I tried minimize the code using a computed prop:
computed: {
info () {
return this.$store.state.foo.bar.tar.info
}
}
Now, I just call info but still, not sure if there is a better way to get values, because sometimes I just need to call info only one in a page, so I have to use the full path or create a computed property for it.
Is there any other way to do this
I always separate vuex into separated modules. For instance if you have store for foo module. I will create file named foo.js which contains
const fooModule = {
state: {
foo: {
bar: {
tar: {
info: 42
}
}
}
},
getters: {
info (state) {
return state.foo.bar.tar.info
}
},
mutations: {
setInfo (state, payload) {
state.foo.bar.tar.info = payload
}
},
actions: {
getInfo ({commit}, payload) {
commit('setInfo', payload)
}
}
}
export default fooModule
Then in your main index vuex, import the module like this way
import Vue from 'vue'
import Vuex from 'vuex'
import fooModule from './foo.js'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
fooModule
}
})
export default store
Then if you wanna get info, you just write your code like this
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters([
'getInfo'
])
}
}
#jefry Dewangga has the the right idea but introducing mapGetters is unnecessary.
VueX by default includes namespacing for modules which allows us to load multiple modules into a store and then reference them with or without namespacing them and it'll figure the rest out.
For Instance if we have the structure of
|-- store
|-- Modules
|-- module1.js
|-- module2.js
|-- module3.js
|-- index.js
We can use index in such a way to bind all of our modules into our Vuex store doing the following:
import Vue from 'vue'
import Vuex from 'vuex'
import modules from './modules'
Vue.use(Vuex)
export default new Vuex.Store({
modules
})
An example of our module1 could be:
const state = {
LoggedIn: true
}
const mutations = {
LOGIN(state) {
state.LoggedIn = true;
},
LOGOFF(state) {
state.LoggedIn = false;
}
}
const actions = {}
export default {
state,
mutations,
actions
}
This in turn gives us the ability to say:
this.$store.commit('LOGIN');
Note that we haven't used any namespacing but as we haven't included any duplicate mutations from within our modules were absolutely fine to implicitly declare this.
Now if we want to use namespacing we can do the following which will explicitly use out module:
this.$store.module1.commit('LOGIN');
MapGetters are useful but they provide a lot of extra overhead when we can neatly digest out modules without having to continuously map everything, unless well we find the mapping useful. A great example of when MapGetters become handy is when we are working many components down in a large project and we want to be able to look at our source without having to necessarily worry about the frontend implementation.