Destructure Reactive object in Vue script setup - vue.js

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?

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

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.

Why is a variable declared in onBeforeMount() not known in the main section of <script>?

I have a Vue3 component in which I use a pre-mount hook (this is a trimmed-down version to scope it to the problem):
<script setup lang="ts">
const hello = () => {
let a = [... allNotes.value]
}
onBeforeMount(() => {
let allNotes = ref([])
}
</script>
With this, I get a ReferenceError: allNotes is not defined on the line that defines a.
Why is it so? Isn't allNotes known to hello() after the mount?
Why are you defining a reactive variable from inside a lifecycle hook? If you want allNotes to be available in the component or in the rest of your script, you only need to declare it in the top level of <script setup>. Remember that the Composition API setup() function replaces the beforeCreate and created lifecycle hooks, so anything defined within setup() will be available everywhere. You can read more about that in the Vue Docs
Specifically the problem here is that allNotes is scoped to the onBeforeMount() function only, and as such isn't known to the rest of your script. Once onBeforeMount() is called and finished, it will destroy allNotes and it will no longer exist.
You can just do
<script setup>
const allNotes = ref([])
const hello = () => {
allNotes.value.push("Hello")
}
</script>
To illustrate the point with the OptionsAPI, what you're doing is the same as something like this:
export default {
beforeMount() {
const allNotes = [];
},
methods: {
hello() {
this.allNotes.push("Hello!");
}
}
}
That won't work, since allNotes only exists inside of the beforeMount() hook. You'd have to declare allNotes in the data() or computed() properties in order for your hello() method to be able to use it.
Also, as a small sidenote, you should declare reactive elements with const and not let. It's a bit weird because you technically are changing its value, but the Vue internals make it so you're actually changing a copy of it that exists inside of Vue. As such, you're not actually modifying the original value, so let is inappropriate.

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

Simple Vue store pattern - initial server fetch not reacting

This app isn't complicated. I'm trying to create a simple store (not keen to use Vuex for something this light) which should coordinate server requests and make sure there's a single source of truth across the app.
store.js
import Vue from "vue"
import axios from "axios"
class Store {
items = []
constructor() {
this.fetchData()
}
fetchData() {
axios
.get("/api/items")
.then(response => this.fillFieldsFromServer(response.data))
}
fillFieldsFromServer(data) {
// NONE OF THESE WORK
// 1: this.items = data
// 2: this.items = this.items.concat(data)
// 3: Array.prototype.push.apply(this.items, data)
}
}
export const itemStore = Vue.observable(new Store())
component.vue
<template>
<ul>
<li v-for="item in items">{{ item }}</li>
</ul>
</template>
<script>
import { itemStore } from "../../stores/item-store.js"
export default {
computed: {
items() {
return itemStore.items
},
},
}
</script>
Obviously I'm fundamentally misunderstanding something here.
What I thought would happen:
The store singleton is created
A server request is fired off
Vue makes the store singleton reactive
The component renders with an empty list
The component watches store.items
The server request returns
The store updates items
The component sees that changes
The component re-renders with the server data
But what's actually happening is that step (8) doesn't occur. The server request returns fine, but the component doesn't see the change so it doesn't re-render.
Obviously I'm doing something wrong. But what?
Vue.observable makes an object reactive by recursively replacing existing properties with get/set accessors, this allows to detect when they are changed. As for arrays, Array.prototype methods that mutate existing array are also replaced to track their calls.
This isn't supposed to work because Array.prototype.push.apply !== store.items.push:
Array.prototype.push.apply(this.items, data)
It should be either:
fillFieldsFromServer(data) {
this.items = data;
}
Or:
fillFieldsFromServer(data) {
this.items.push(...data);
}
Here is a demo.