Vue3 prop updates title attribute but not computed prop - css

I'm using an external library rendered using Vue3. It has the following component from a third part library [Edit: I realize the GitHub repo for that library is out of date, so updating with the actual code in my node_modules.]
<template>
<div class="socket" :class="className" :title="socket.name"></div>
</template>
<script>
import { defineComponent, computed } from "vue";
import { kebab } from "./utils";
export default defineComponent({
props: ["type", "socket"],
setup(props) {
const className = computed(() => {
return kebab([props.type, props.socket.name]);
});
return {
className
};
}
});
</script>
It renders based on a Socket object passed as a prop. When I updated the name property of the Socket, I see the title updated accordingly. However, the CSS/class does not update. I've tried $forceRefresh() on its parent, but this changes nothing.
Update: I was able to move the rendering code to my own repo, so I can now edit this component if needed.
Based on this updated code, it seems the issue is that the class is computed. Is there any way to force this to refresh?
The only time it does is when I reload the code (without refreshing the page) during vue-cli-service serve.
For reference, the | kebab filter is defined here:
Vue.filter('kebab', (str) => {
const replace = s => s.toLowerCase().replace(/ /g, '-');
return Array.isArray(str) ? str.map(replace) : replace(str);
});
Do filtered attributes update differently? I wouldn't think so.
I was also wondering if it could be a reactivity issue, and whether I needed to set the value using Vue.set, but as I understand it that's not necessary in Vue3, and it's also not consistent with the title properly updating.

Computed properties are reactive, however Vue does not expect you to mutate a prop object.
From the documentation:
Warning
Note that objects and arrays in JavaScript are passed by reference, so
if the prop is an array or object, mutating the object or array itself
inside the child component will affect the parent state and Vue is
unable to warn you against this. As a general rule, you should avoid
mutating any prop, including objects and arrays as doing so ignores
one-way data binding and may cause undesired results.
https://v3.vuejs.org/guide/component-props.html#one-way-data-flow
I know that this says, that you should not mutate it in the child, but the general rule is, that you should not mutate properties at all, but instead create new object with the modified data.
In your case the computed function will look for changes in the properties itself, but not the members of the properties, that is why it is not updating.

Related

do you need to emit event for reactive objects in vue3?

i've not really understood how v-model works for components, check my code:
<template>
<a-form-item name="bananas" label="bananas">
<a-input v-model:value="test.bananas" />
</a-form-item>
</template>
<script>
import { computed, defineComponent } from 'vue'
export default defineComponent({
props: {
formData: {
type: Object,
required: true
}
},
setup(props) {
const test = computed(() => props.formData)
return { test }
}
})
</script>
where formData in parent component is a reactive object
<InnerComponent v-model:formData="formData" />
const formData = reactive({
name:'',
.....
bananas: 'bananas',
})
this code "works", or rather it seems that the formData object is updated when the input "bananas" is changed .... but how?
reading the documents using v-model in the components i should also define an update function for it, also this is an object and there are no examples using responsive objects
Can someone explain?
all this because having to create a very large form, I need to divide the various sections into sub-components, passing the reactive object to all the children
from https://stackoverflow.com/a/65604790/197546
v-model is syntactical sugar for :value and #change
Instead of <input v-model="name">, you could use
<input :value="name" #update:model-value="v => name=v">
If you are using v-model, you don't need to use emits and listeners. In vue 3 you can specify which variable you'd like to apply this syntactic sugar to via the colon definition, as you have done with v-model:formData. This allows components to have more than one variables be available through v-model which can be very helpful in more advanced component setups.
if you're passing a reactive variable you don't need to use v-model at all, since reactive exposes Vue's internals so in the case of reactive the reactivity is handled separate of the parent-child interaction. Whether the reactive is passed to the child as a prop or from a global reference, the reactivity will work across all instances.
On a side note, refs will not allow this in the same way. while you can still pass them as global, passing it as a prop will not work. It will behave like a regular prop, in the it will continue to receive updates from the parent, but local changes do not propagate outside of the component scope.

Passing data to props after asynchronous call in Vue

I have set up a bare bones vue project to show the problem. The only thing I added was the axios package. The problem is when I try to set the property of child component after an asynchronous call I cant read that property in the component. If you look at the code you can see I console log several times to show when I can get the data and when I cant. Please help me figure out what im missing here.
Parent
<template>
<div id="app">
<HelloWorld :test_prop="testData" :test_prop2="testData2" :test_prop3="testData3" test_prop4="I work also"/>
<div>{{testData5}}</div>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import axios from 'axios';
export default {
name: 'app',
components: {
HelloWorld
},
data() {
return {
testData: '',
testData2: 'I work just fine',
testData3: '',
testData5: ''
}
},
created: function(){
var self = this;
this.testDate3 = 'I dont work';
axios.get('https://jsonplaceholder.typicode.com/posts/42').then(function(response){
//I need this one to work
self.testData = 'I dont work either';
self.testData5 = 'I work also';
});
}
}
</script>
Child
<template>
</template>
<script>
export default {
name: 'HelloWorld',
props: ['test_prop', 'test_prop2', 'test_prop3', 'test_prop4'],
data() {
return {
comp_data: this.test_prop,
comp_data2: this.test_prop2,
comp_data3: this.test_prop3,
comp_data4: this.test_prop4
}
},
created: function(){
console.log(this.test_prop);
console.log(this.test_prop2);
console.log(this.test_prop3);
console.log(this.test_prop4);
}
}
</script>
Your console.log inside created hook will show you the initial state of this variables in Parent component. That's because Parent's created hook and Child's created hook will run at the same time.
So, when you solve your promise, Child component was already created. To understand this behavior, put your props in your template using {{ this.test_prop }}.
To solve it, depending on what you want, you can either define some default value to your props (see) or render your child component with a v-if condition. That's it, hope it helps!
On Vue created hook only the initial values of properties passed from main component. Therefore later updates (like your example "after ajax call") in main component will not effect to child component data variables because of that already child created hook take place.
If you want to update data later one way you can do like this:
watch: {
test_prop: function(newOne){
this.comp_data = newOne;
}
}
Adding watcher to property changes will update the last value of property from main component.
And also edit the typo this.testDate3. I guess it must be this.testData3

what does createContainer in Meteor using React do?

I'm working on the Meteor using React tutorial and trying to understand createContainer(). From reading here:
http://guide.meteor.com/v1.3/react.html#using-createContainer
I think that its a function defined in meteor/react-meteor-data that is used for data loading. In this specific case, it retrieving data from the Mini Mongo Database (named Task here). My question is, what does the second argument to createContainer do? (named App here). Thank you!
class App extends Component {
//...class definition
}
export default createContainer(() => {
return {
//Tasks is a Mongo.Collection
//returns the matching rows (documents)
//here we define the value for tasks member
tasks: Tasks.find({}, { sort: { createdAt: -1} }).fetch(),
};
}, App);
A component created with createContainer is a simple wrapper around your actual component, but it's powerful in that it handles Meteor's reactivity for you so you don't have to think about how to keep your everything up to date when your data changes (e.g. a subscription loads, ReactiveVar / Session var changes)
A React component is basically just a JavaScript function, it is called with a bunch of arguments (props) and it produces an output. React doesn't know if your data has changed unless you tell it so. The component created with createContainer will re-render when your reactive data changes and send a new set of props to your actual component.
The options for createContainer are a function that returns the reactive data you want, and the component you want to wrap. It's really simple, and the render function for createContainer is literally one line:
return <Component {...this.props} {...this.data} />;
It passes through any props you pass to the wrapped component, plus it adds the reactive data source you set up.
You can see the code for yourself here: https://github.com/meteor/react-packages/blob/devel/packages/react-meteor-data/createContainer.jsx
The <Component {...this.props} syntax is known as a splat and basically turns:
{
prop1: 'some value',
prop2: 'another value'
}
into:
<Component prop1='some value' prop2='another value />
(See: https://facebook.github.io/react/docs/jsx-spread.html)
Asking a coworker, this is the answer that I got:
createContainer's second argument is the class name that you want the data to be encapsulated in. It will then have "reactive data" because every time the data in the DB is changed, the class's props will change to include the new data.
Also, the createContainer() function should be called outside of the class definition.
If anyone has anything to add please feel free to contribute.
createContainer's second argument is the name of the class in which you want to pass on the props to.
Lets say createContainer returns a prop called firstName
Now, whenever there is a new firstName entry or an updated firstName in the db, then createContainer is going to call the second argument which is our class name with the prop that it holds i.e firstName.
I hope that makes sense.

How to Two-way Data Binding Between Parents and grandchildren in Vue.js

I faced a problem, I solve it by cookies but I want to solve the problem without cookies. I have a component which called app-header and It has another component which called outmodal.
Now, My first Vue instance require component app-header.
var vue = new Vue({
el : "html",
data : {
title : "Site Title",
description : "description of page",
keywords : "my keywords",
view : "home",
login : "login"
},
components:{
"app-header" :require("../../components/header"),
"app-footer" :require("../../components/footer"),
"home" :require("../../views/home")
},
});
code of app-header
var Vue = require("vue");
Vue.partial("login",require("../../partials/login.html"));
Vue.partial("logged",require("../../partials/logged.html"));
module.exports = {
template : require("./template.html"),
replace : true,
components : {
outmodal : require("../outmodal")
},
props : ['login']
}
code of outmodal
var Vue = require("vue");
Vue.partial("loginModal",require("../../partials/loginModal.html"));
module.exports = {
template : require("./template.html"),
replace : true,
props : ['name'],
data : function () {
return {
userLogin : { mail : "", password : "", remember : ""}
}
},
methods : {
formSubmit : function(e){
e.preventDefault();
this.$http.post("http://example.com/auth/login",{ "email": this.userLogin.mail , "password": this.userLogin.password },function(data,status,request){
$.cookie("site_token",data.token,{expires : 1})
}).error(function(data,status,request){
});
}
}, ready : function(){
console.log("it works")
}
}
In outmodal component I connect the API and I check the login, If login will be succesfull, I want to change value of login variable in my Vue instance. I use web pack to build all requires. So I don't know how can I data binding between these files.
How can I solve It? I
The Best Solution which I found
For 0.12
http://012.vuejs.org/guide/components.html#Inheriting_Parent_Scope
for 1.0
http://v1.vuejs.org/guide/components.html#Parent-Child-Communication
for 2.0
https://v2.vuejs.org/v2/guide/components.html#Composing-Components (use props to one-way bind data from parent to child)
There are several ways of doing it, and some are mentioned in other answers:
Use props on components
Use v-model attribute
Use the sync modifier (for Vue 2.0)
Use v-model arguments (for Vue 3.0)
Use Pinia
Here are some details to the methods that are available:
1.) Use props on components
Props should ideally only be used to pass data down into a component and events should pass data back up. This is the way the system was intended. (Use either v-model or sync modifier as "shorthands")
Props and events are easy to use and are the ideal way to solve most common problems.
Using props for two-way binding is not usually advised but possible, by passing an object or array you can change a property of that object and it will be observed in both child and parent without Vue printing a warning in the console.
Because of how Vue observes changes all properties need to be available on an object or they will not be reactive.
If any properties are added after Vue has finished making them observable 'set' will have to be used.
//Normal usage
Vue.set(aVariable, 'aNewProp', 42);
//This is how to use it in Nuxt
this.$set(this.historyEntry, 'date', new Date());
The object will be reactive for both component and the parent:
I you pass an object/array as a prop, it's two-way syncing automatically - change data in the
child, it is changed in the parent.
If you pass simple values (strings, numbers)
via props, you have to explicitly use the .sync modifier
As quoted from --> https://stackoverflow.com/a/35723888/1087372
2.) Use v-model attribute
The v-model attribute is syntactic sugar that enables easy two-way binding between parent and child. It does the same thing as the sync modifier does only it uses a specific prop and a specific event for the binding
This:
<input v-model="searchText">
is the same as this:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
Where the prop must be value and the event must be input
3.) Use the sync modifier (for Vue 2.0)
The sync modifier is also syntactic sugar and does the same as v-model, just that the prop and event names are set by whatever is being used.
In the parent it can be used as follows:
<text-document v-bind:title.sync="doc.title"></text-document>
From the child an event can be emitted to notify the parent of any changes:
this.$emit('update:title', newTitle)
4.) Use v-model arguments (for Vue 3.0)
In Vue 3.x the sync modifier was removed.
Instead you can use v-model arguments which solve the same problem
<ChildComponent v-model:title="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent :title="pageTitle" #update:title="pageTitle = $event" />
5.) Use Pinia (or Vuex)
As of now Pinia is the official recommended state manager/data store
Pinia is a store library for Vue, it allows you to share a state across components/pages.
By using the Pinia store it is easier to see the flow of data mutations and they are explicitly defined. By using the vue developer tools it is easy to debug and rollback changes that were made.
This approach needs a bit more boilerplate, but if used throughout a project it becomes a much cleaner way to define how changes are made and from where.
Take a look at their getting started section
**In case of legacy projects** :
If your project already uses Vuex, you can keep on using it.
Vuex 3 and 4 will still be maintained. However, it's unlikely to add new functionalities to it. Vuex and Pinia can be installed in the same project. If you're migrating existing Vuex app to Pinia, it might be a suitable option. However, if you're planning to start a new project, we highly recommend using Pinia instead.
i found this one to be more accurate.
https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
only in 2.3.0+ tho.
and honestly it's still not good enough. should simply be a easy option for 'two-way' data binding. so none of these options is good.
try using vuex instead. they have more options for such purpose.
https://vuex.vuejs.org/en/state.html
I would prefer event-driven updates as recommended in the documentation. However, I was limited by the existing ("third-party") component already using props and $emit. This component is my grandchild. The following is my solution (passing value through child using props, sync and computed value with $emit.
Comments are welcome.
Value can be modified in parent and grandchild without error:
Grandchild (simplified third-party component):
<template>
<div v-show="value">{{ value}}</div>
<button #click="closeBox">Close</button>
</template>
<script>
export default {
props: {
value: null
},
methods: {
closeBox() {
this.$emit('update:value', null);
}
}
}
</script>
Child:
<template>
<grandchild-component :value.sync="passedValue" />
</template>
<script>
export default {
props: {
value: null
},
computed: {
passedValue: {
get() {
return this.value;
},
set(newVal) {
this.$emit('update:value', newVal);
}
}
}
}
</script>
Parent:
<template>
<child-component :value.sync="value" />
</template>
<script>
export default {
data() {
return {
value: null,
}
},
// ... e.g. method setting/modifying the value
}
</script>

getMeteorData race conditions with component lifecycle?

I'm getting some pretty undesirable behavior in my app, and I'm having a hard time replicating the issue and/or figuring out what I'm doing wrong or not understanding about React that's causing my components to act this way.
What I want to do is to get some data from Mongo on the App component, then have all of that data readily available for any child that I want.
<App> //get data here, pass to children through props
<ChildElement1 data={this.data.appData}/>
<ChildElement2 data={this.data.appData}/>
<ChildElement3 data={this.data.appData}/>
</App>
Here's how I've attempted to tackle this with React so far:
App = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
let _id = 'exampleId';
return {
appData: Collection.findOne({_id})
};
},
render() {
return (<ChildElement1 data={this.data.appData} />);
}
});
ChildElement1 = React.createClass({
getInitialState() {
return {
values: ['val1', 'val2', 'val3', 'val4'] //default values
};
},
componentWillMount() {
if(this.props.data.specificValues) {
this.setState({values: this.props.data.specificValues});
}
},
render() {
let values = this.state.values;
return (<span>{values[0]} {values[1]} {values[2]} {values[3]}</span>);
}
});
So here's where it gets weird. When I call componentWillMount(), sometimes this.props.data is defined and other times it's not, which leads me to believe there's some sort of race conditions going on where sometimes that data gets loaded correctly as a prop and other times it doesn't.
I then figured that, well okay, I can't depend on the data prop being there before the component is initially mounted, so I could instead use componentWillReceiveProps(nextProps) and check the updated props that way (and update the state, if necessary). HOWEVER! After using componentWillReceiveProps, now this.props.data is seemingly ALWAYS correctly attached to the props of ChildElement1 (which means componentWillReceiveProps doesn't run!).
My final solution was to use BOTH componentWillMount and componentWillReceiveProps to account for both situations and to do the exact same check in both locations. This fix works, but boy does it seem messy and probably indicates a lack of understanding of component lifecycles, how the meteor/react should properly interact, both, or something else completely.
I'd sure appreciate a bit of help here.
edit: I've come up with a small improvement - instead of using componentWillMount and componentWillReceiveProps to do the check to see if there are specific values defined in the Mongo Collection, I put that logic in render like so:
render() {
let data = this.props.data,
values = (data) ? data.specificValues : this.state.values;
return (<span>{values[0]} {values[1]} {values[2]} {values[3]}</span>);
}
There's definitely still some sort of underlying issue, however, as I still don't understand why this.props is so inconsistent when given data retrieved from getMeteorData. This version is a bit more succinct, however.
I found a better approach to this rather than passing the data returned from getMeteorData to each of the children as props. Using the methods described here: https://www.tildedave.com/2014/11/15/introduction-to-contexts-in-react-js.html, I explicitly listed the childContextTypes and getChildContext in <App /> and then contextTypes in <ChildElement1 />, which allows this.data.appData to be available by way of this.context in <ChildElement1 /> and presumably within any other children of <App />. Although I gotta admit, declaring every single proptype of the collection is a major PITA, seems like it'd be necessary to write a mixin (or rather, a bunch of mixins) to handle that stuff.

Resources