Is ref() & watch() combination equivalent to computed() in Vue 3? - vue.js

I have a reactive object
team
and it has a property
players
I want to also make players reactive (ease of use by exporting it from a custom hook).
Are these two pieces of code equivalent ?
(I suppose they are not, but I want to know the gotchas of using one over the other way)
const players = computed(() => team.value.players);
const players = ref(team.value.players);
watch(team, () => players.value = team.value.players);
Which one should I use ? Is there a big difference (optimization ?)
What about usage with v-model ?

It's recommended to use computed property with getter/setter in order to be able to bind it to v-model :
const players = computed({
get() => team.value.players,
set(newVal){
}
});

Related

Vue computed() not triggered on reactive map

I have a reactive around a map that's initially empty: const map = reactive({});, and a computed that tells if the map has a key "key": const mapContainsKeyComputed = computed(() => map.hasOwnProperty("key")). The computed doesn't get updated when I change the map.
I stuck with this issue for a day and managed to come up with a minimum example that demonstrates the issue:
<script setup>
import {computed, reactive, ref, watch} from "vue";
const map = reactive({});
const key = "key";
const mapContainsKeyComputed = computed(() => map.hasOwnProperty(key))
const mapContainsKeyWatched = ref(map.hasOwnProperty(key));
watch(map, () => mapContainsKeyWatched.value = map.hasOwnProperty(key))
</script>
<template>
Map: {{map}}
<br/>
Computed: does map contain "key"? {{mapContainsKeyComputed}}
<br/>
Watch: does map contain key? {{mapContainsKeyWatched}}
<br/>
<button #click="map[key] = 'value'">add key-value</button>
</template>
I've read a bunch of stackoverflow answers and the Vue docs, but I still can't figure it out.
why mapContainsKeyComputed doesn't get updated?
if the reactive doesn't "track" adding or removing keys to the map, why the Map: {{map}} (line 14) updates perfectly fine?
when I replace the map{} with an array[] and "hasOwnProperty" with "includes()", it works fine. How's that different?
how do I overcome this issue without the ugly "watch" solution where the "map.hasOwnProperty(key)" has to be duplicated?
EDIT: as mentioned by #estus-flask, this was a VueJS bug fixed in 3.2.46.
Vue reactivity needs to explicitly support reactive object methods. hasOwnProperty is rather low-level so it hasn't been supported for some time. Without the support, map.hasOwnProperty(key) tries to access key on non-reactive raw object and doesn't trigger the reactivity, so the first computed call doesn't set a listener that could be triggered with the next map change.
One way this could be fixed is to either define key initially (as suggested in another answer), this is the legacy way to make reactivity work in both Vue 2 and 3:
const map = reactive({ key: undefined })
Another way is to access missing key property on reactive object:
const mapContainsKeyComputed = computed(() => map[key] !== undefined)
Yet another way is to use in operator. Since Vue 3 uses Proxy for reactivity, that a property is accessed can be detected by has trap:
const mapContainsKeyComputed = computed(() => key in map)
The support for hasOwnProperty has been recently added in 3.2.46, so the code from the question is supposed to be workable with the latest Vue version.
map is not really a map. This would be different in any Vue 3 version if Map were used, it's supported by Vue and it's expected that map.has(key) would trigger reactivity.

How do I clear a reactive array in Vue3 using composition?

I have the following in my project...
const basicRecords = reactive([]);
It basically just houses an array of objects. I want to be able to refresh this array, that would require me to remove all of the records and add them back. To do this I have done the following...
basicRecords.splice(0);
basicRecords.push(...recordResult.data);
This seems to work but also seems super hacky.
What is the proper way to reset a reactive array in Vue3?
Try to use Object.assign to keep it reactive :
const basicRecords = reactive([]);
Object.assign(basicRecords, recordResult.data)
or define basicRecords as inner field of reactive state :
const state = reactive({basicRecords : []});
state.basicRecords = recordResult.data
or with ref :
const basicRecords = ref([]);
basicRecords.value = recordResult.data

Where to put parameterised mapGetters (in computed or methods of component)

In vuex, I have this:
getByLessonId: state => _lessonId => {
return state.entities.filter(e => e.lesson.id === _lessonId);
},
In component:
// using as a method...
...mapGetters("assignment", { getAssignmentsByLessonId: "getByLessonId" }),
Later in the code, since the mapping returns the function, I need to call it like this?
// load this lessons assignments...
this.assignments = this.getAssignmentsByLessonId()(this.id);
// this is what i started with
// this.$store.getters["assignment/getByLessonId"](this.id)
It works, just not sure if there is a better way to do this? Or should I put mapGetters in the computed properties of the component?
Better put mapGetters (as long as mapState) in a computed section of a component. This is a recommended way to use several getters or props of a state because you have one place only to control getters and props in a component.

Can I create a mobx computed inside a React render function to use like useMemo()?

I'm wondering how to go about using a mobx observable inside a useMemo hook. I know I could pass all possibly dependencies to the hook, but that could get kind of messy:
const MyComponent = observer(() => {
const people = useGetPeople();
const peopleFormatted = useMemo(() => {
return people.map(person => person.fullName);
},[ ...? ]);
});
I can't easily make every person's firstName be a dependency of useMemo. I'd think I could extract the functionality to a computed ... but I feel like this won't work:
const MyComponent = observer(() => {
const people = useGetPeople();
const peopleFormatted = computed(() => {
return people.map(person => person.fullName);
});
});
I feel like it will confuse mobx to create a computed inside a reaction that the reaction must depend on.
I know I could extract the computed to each person but I don't feel like that's a solution that matches every use case.
Thanks in advance!
Assuming const people = useGetPeople(); is an observable array of some sort of people objects...
const peopleFormatted = computed(() => {
return people.map(person => person.fullName);
}).get(); //note .get()
Should work fine inside the observer function body. See https://mobx.js.org/computeds-with-args.html#2-close-over-the-arguments
What is confusing me is useGetPeople();
That typically means you are using react's state api for managing state and reactions. ie: useState, etc.
Without seeing what useGetPeople() does under the hood, it's hard to give a concrete answer.

Passing props to child component Cyclejs

I m studying CycleJs and I m looking for a proper way to handle passing props to child component.
Actually, I m having the following stuff :
import {div, input} from '#cycle/dom'
export function App(sources) {
const inputOnChange$ = sources.DOM.select('input').events('input')
const streamofResult = inputOnChange$
.map(e => e.target.value)
.startWith('')
.map(defaultInput => {
const title = Title({value: defaultInput})
return div([
title,
input({attrs: {type: 'text'}})
])
})
const sinks = {DOM: streamofResult}
return sinks
}
export function Title(sources) {
return div(sources.value)
}
It simply allows to make some inputs, and to display it in a child component called Title.
I think I should use a stream to handle passing props to my child.
But I don't understand why it would be a better solution in this simple to use a stream instead of a primitive ?
There is something that I probably have not understood.
You haven't misunderstood anything. There is no right answer. If you know for a fact you'll never want to change the props after initialization then you could pass the props as a primitive, but the more common convention is to send a props$ since it's not much costlier to do something like O.of(x) vs x (assuming RxJS) and using streams everywhere is consistent with the philosophy of the framework. Additionally, there are occasions when you'll want to change the properties dynamically after component initialization, where a stream is appropriate.
Keeping a consistent props or props$ convention for all your components can make reading the code easier since you won't have to think, "Does this component use a primitive or a stream for props...?"