Destructure a local ref object into properties keeping reactivity in Vue 3 Composition API - vue.js

Given a Vue 3 component which accepts a user prop of type Object like so:
{
name: 'Foo',
age: 52
}
I'd like to work with this object locally, that is, mutations of its properties shall not be propagated to props.user. However, I'm struggling with how to destructure the properties name and age from the local copy.
<script setup>
const props = defineProps({
user: {
type: Object,
required: true,
}
});
// create a local copy of user using Lodash's cloneDeep
// localUser shall not be connected to props.user
const localUser = ref(cloneDeep(props.user));
// How to create local refs for name, age keeping reactivity to localUser?
// Variant 1
const name = toRef(localUser, 'name');
const age = toRef(localUser, 'age');
// Variant 2
const { name, age } = toRefs(localUser);
</script>
Which of both variants would be the correct way? Or maybe it's recommended to do it totally different?

Related

Using script setup and reactive state vue 3 with toRefs

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

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?

Access data on main component from composable - VueJS 3

I was wondering what could be the best approach to access data on my setup function, from a composable method... If it is possible at all (and a good practice).
For example, on my main component, I would have a reactive data object with all of the variables used in that component... but not all of them will be used by my composable.
Here's an example of what I'm trying to do (It's kinda like a model composable)...
Let's say that I have a useDrivers composable, and want to use its saveDriver method, something like this:
// all shortened for readability
import useDrivers from "#composables/use-drivers";
export default {
setup() {
const { saveDriver } = useDrivers();
const data= reactive({
foo: null,
bar: null,
accident: { ... },
driver: { ... },
anotherUsedVar: null,
});
// instead of doing something like this:
saveDriver(driver, accident.id, anotherUsedVar);
// or even worse:
saveDriver(driver.name, driver.gender, driver.license, accident.id, anotherUserVar);
// Could I, somehow, access my DATA const from inside my method?
// but, again, without having to pass whatever amount of parameters
saveDriver();
}
}
If data is static, it can be a single context object:
const context = { name: driver.name, ... };
saveDriver(context);
If data is supposed to stay reactive inside saveDriver, a computed ref could be passed:
const context = computed(() => ({ name: driver.name, ... });
saveDriver(context);
Then data needs to be processed inside saveDriver with usual composition API - computed, watch, etc.

vuex: state field "foo" was overridden by a module with the same name at "foo"

I am getting this warning in the console:
[vuex] state field "foo" was overridden by a module with the same name at "foo"
What does it mean and what can I have done wrong?
This is a new warning added in Vuex 3.1.2.
https://github.com/vuejs/vuex/releases/tag/v3.1.2
It is logged when a property name within the state conflicts with the name of a module, like so:
new Vuex.Store({
state: {
foo: 'bar'
},
modules: {
foo: {}
}
})
If you attempt to access state.foo you might expect the value to be 'bar' but it will actually refer to the state of the foo module.
You would fix this problem by removing the property from the state object, or by renaming either the property or the module.
Here's a small example that logs the relevant warning and shows the resulting value of state.foo:
const store = new Vuex.Store({
state: {
foo: 'bar'
},
modules: {
foo: { state: { flag: 2 } }
}
})
console.log(store.state.foo)
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<script src="https://unpkg.com/vuex#3.4.0/dist/vuex.js"></script>
Update:
This warning can also be logged if you create multiple stores using the same configuration object, e.g. during testing.
Here's an example:
const config = {
state: {},
modules: {
foo: {}
}
}
const store1 = new Vuex.Store(config)
const store2 = new Vuex.Store(config)
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<script src="https://unpkg.com/vuex#3.4.0/dist/vuex.js"></script>
The problem is that the configuration includes a root state object. The first store adds the foo module to that object. When the second store tries to do the same thing it finds that the property already exists and logs the error.
If the root state object is empty, like it is in my example, then it could just be removed. However, assuming it isn't empty you'd fix this by changing it to a function instead:
const config = {
state () {
return {
/* state properties here */
}
},
modules: {
foo: {}
}
}
This works exactly the same way as the data function for a component.

In Vue - what's the difference between this.$data.foo vs this.foo?

I'm curious since both seem to work and I'm having trouble finding an answer in the Vue docs. Is there a reason you should refer to data in Vue as this.$data.whatever vs just this.whatever?
The data with $ prefix is specifically defined for accessing vue data property rather than user defined property.
For eg.
var data = { foo: 'foo' } // user defined data
var inst = new Vue({ data: { foo: 'foo' } }) // data - built-in vue property
data.foo // user defined data
inst.$data.foo // vue property - data
When you're inside the Vue hooks, you can just simply use this.foo for eg. inside computed method.
For more detail, you can see the docs
The $data attribute is used to access the data property outside the component:
var data = { a: 1 }
// direct instance creation
var vm = new Vue({
data: data
})
vm.a // => 1
vm.$data === data // => true
// must use function when in Vue.extend()
var Component = Vue.extend({
data: function () {
return { a: 1 }
}
})
Source: https://v2.vuejs.org/v2/api/#data