-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
$refs should be reactive to be able to use them in computed properties #3842
Comments
How about using one handler with multiple parameters?
inside components:
|
Because you can have multiple of the same type of fields. |
Try something like this: <template>
<form>
<my-input v-model='fields.email'></my-input>
<my-input v-model='fields.password'></my-input>
<p>This is {{ isValid ? 'valid' : 'invalid' }}</p>
</form>
</template>
<script>
export default {
data () {
return {
fields: {
email: { value: '', valid: false },
password: { value: '', valid: false },
}
}
},
computed: {
isValid () {
return Object.values(this.fields).every(field => field.valid)
}
}
}
</script> Where the input components |
So this is where I get confused.
Or something? The main issue I have with this, is that I need all those fields in my state, whereas previously I didn't, because I could just use the refs. Having them in my state is a pretty major pain in the ass, because it clutters my state without a good reason. |
@TheDutchCoder This is inconvenient indeed. :( @yyx990803 Do you have something in mind for situations like these? |
Just to add some context, this is what I could do previously: computed: {
isReady() {
return this.$refs.email.isValid && this.$refs.password.isValid
}
} But now I need to add additional state and a handler to deal with this: data() {
return {
email: { value: '', isValid: false },
password: { value: '', isValid: false }
}
}
// Some method that dynamically handles input changes
// Computed prop
computed: {
isReady() {
return this.email.isValid && this.password.isValid
}
} Mainly the extra (not very useful) state and the handler are a bit of a pain, would be great if there could be some alternative to what used to be $refs that remain reactive. Not sure what the implications would be and why it was deprecated, I'm just trying to illustrate a use case when the old $refs were very useful. |
Would it be ok to rename the issue to something like: "$refs should be reactive to be able to use them in computed properties"? |
Sure go for it! Thanks for the discussion guys, it;s appreciated. |
If a component emits an <template>
<input :value='value.value' @input="onInput($event)">
</template>
<script>
...
props: ['value'],
...
onInput(event) {
this.$emit({ value: event.target.value, valid: this.someValidatorFunction() })
}
...
</script> I imagine there's a better way, though. Without reactive refs, that is. Perhaps using mixins, somehow... I'm still for reactive refs, though. It's a super useful thing to have in general. |
Alright, at least your example works. Since the Vuex getters aren't yet available when the state is defined, we can't use them to populate the values. We could use |
Here's two repos that compare the two scenarios: Vue1: https://jsfiddle.net/okv0rgrk/8330/ I hope this clearly illustrates the current problem. The biggest issue right now is the fact that you can't use computed props in the child component anymore. Even |
I've also found reactive $refs to be useful in unit tests.... like so:
Is there some suggested way to replace this sort of pattern? Otherwise it would be nice for this to work again. |
I have to agree with @TheDutchCoder Using refs is an easy way for parent to read child information. Which inadvertently makes $refs seem useless without reactivity. I commonly also use this practice, to check the validity of ref components to validate the parent component. |
Do you have access to <my-input @validity-changed="isValid=$event"></my-input>
<my-input @validity-changed="isValid=$event"></my-input>
<p>This is {{ isValid ? 'valid' : 'invalid' }}</p> // with
data: function() {return {isValid:true}} for a normal Input I would try a computed setter: template: "<input v-model='username'></input>",
computed: {
username: {
get: function() { return this.name }
set: function(val) {
this.valid = this.validate(val)
if (this.valid) {
this.name = val
}
}
}
},
data: function() {
return {name:"name", valid: true}
} I'm using |
Using Vue in Meteor, I really miss this feature. But in Meteor's default frontend Blaze, $ref was available as a part of 3rd party package, not as part of the core. So if it's not possible in the core in Vue2, at least having it as a plugin would be a valuable option. |
This is indeed inconvenient. I'm trying to use computed properties to control the appearance of an element based on a property of two of its siblings: <h1 v-if="showTitle">codus</h1>
<modal ref="loginModal">
...
</modal>
<modal ref="signupModal">
...
</modal> computed: {
showTitle() {
return !(this.$refs.loginModal.shown || this.$refs.signupModal.shown);
},
}, I believe this is a legitimate use case and it'd be nice to be able to do something like this. However, it seems console.log(this.$refs);
setTimeout(() => console.log(this.$refs), 1000);
Thoughts? |
Another use case I'm running into is testing whether a component is focused to use it to control state: <template>
<div class="my-component-wrapper">
<div class="my-component" tabindex="0"></div>
</div>
</template> computed: {
hasFocus() {
return this.$refs.myElement === document.activeElement
}
} Right now, I'm listening for |
Well, that use case would not even work if |
Ah good call, technically |
Can't recall if the focus event bubbles, but if it does, register an event listener in |
On second thought that could lead to unnecessary rerenders. |
The simplest workaround is using |
I also find situations when reactive Anyway, i think getting the children's state from parent is natural and straightforward. Without reactive feature, |
Of course everyone tries to design components that way. But when you have sibling components like in my case, where a button lives in a different slot of a parent, I think $refs to components actually make the code cleaner instead of cluttering it with meaningless container components which hold part of the state of their children. |
It's true, I see your point as well. I imagine the middle ground would be something like distinguished public and private properties/computeds/methods. That way a component's interface could remain well-defined and components could take useful references on their children. |
The more I think about it, the more I kinda like that idea. Could maintain compatibility by making everything public by default, but if someone wants to privatize data they could specify which fields are part of the public interface by using a property like "public": ["field1", "computed2", "method3"]. Then component refs could be proxy objects that only contain the exposed members. |
Any updates on this? |
You can use a "bus" instance (for example
This is not that meaningless and may be a good solution to make this maintainable. What is your use case? |
|
Hi. An expression by <span :key="typeof $refs.myChild">
Child is an {{ typeof $refs.myChild }}
</span>
<my-component ref="myChild"/> Thank you. |
If $refs were reactive, they could also be watched. 👍 |
Is there any progress with this feature? |
I see the problem reactive refs solve. But I also think there's a good reason why they're not reactive. As it says in the docs, refs are "only meant as an escape hatch for direct child manipulation - you should avoid accessing $refs from within templates or computed properties." As we all know, it's an important pattern that components are kept isolated and communicate through the props and events interface. Refs only exist for edge cases where this pattern is insufficient. Once refs are reactive, it'll allow for an anti-pattern where refs are used instead of events which will result in components being coupled. So I agree that it's messy making forms with Vue where the inputs are in separate components, and no doubt reactive refs would be convenient here. I just think it's too easy for them to be abused, so a better solution should be considered. |
For me, non-reactive Right now, I'm working on a dropdown component and I use I agree with @anthonygore
Conventions should be followed, no doubt, but doing things the right way is a responsibility of the developer. What a great tool like Vue should do is to give you as much functionality as it can while remaining efficient. People break rules all the time and stripping away useful functionality in an attempt to prevent that is not the right way to handle things, in my opinion. It's the tool's job to give you options, it's your job to educate yourself and do things right. |
The biggest issue with If your problem depends on |
Why not? You can depend on computed properties? And so if we see You can make a cycle with computed properties and you can make For example, imagine that I want a counter of how many visible components are displayed as children, where component can contain logic to hide itself or not. I can duplicate this logic and have on the parent component logic which counts how many components should be visible. Or I can simply just count the visible components in |
I've had luck with including a dummy test on a reactive prop/data/computed value, and then returning an element dom property: <my-input v-model="email" ref="email"></my-input>
<my-input v-model="password" ref="password"></my-input>
<p>This is {{ isValid ? 'valid' : 'invalid' }}</p> computed: {
isValid() {
// dummy test to make this reactive to changes
if (this.email === this.$el || this.password === this.$el) {
// this return will never happen, but by 'getting' the v-models values registers
// this computed prop as reactive to changes in the form input values
return
}
return this.$refs.email.isValid && this.$refs.password.isValid
}
} If you need to react to changes in the DOM (i.e. adding new child elements, attribute changes), you can use a |
Just to add my own two cents on this, my work around for non-reactive $refs involved using v-bind.sync. That allows me to hold some state in the parent that is updated as needed by the children. While a $refs approach was more intuitive in my case, v-bind.sync did end up cleaner. |
I've almost completely eliminated my own needs for If you have a pair of components that need to be coupled together, you make it available to children through Consider this simplified RadioGroup component snipped from a real component that I'll be using in production soon: export default {
name: "RadioGroup",
provide() {
return { $radio: this } // Be careful how you name this to not overlap with Vue.js!!
},
data: () => ({
inputs: []
},
methods: {
register(input) {
this.inputs.push(input)
input.$on("hook:beforeDestroy", () => spliceItem(this.inputs, input))
// Do anything else needed for initialization
}
}
} Then, child components can register with their parent like so: export default {
name: "RadioOption",
inject: {
$radio: { default: null }
},
created() {
if (!this.$radio) throw new Error()
this.$radio.register(this)
}
} A few benefits come along with this method:
|
@sirlancelot I understand what you're doing here. But I don't see where you defined I also wanted to ask if you'd recommend |
@mesqueeb I would recommend |
Yes, No, you don't need to use |
Although I don't totally approve having reactive $refs as it can show a bad design choice in the model structure, I think it has its valid use cases so I created a small plugin to make |
@posva why would it be a bad choice to use reactive refs. In your plugin, you've used |
Wanted to share this approach I just stumbled onto. Again, using a .sync approach or event is clearly recommended and preferred. However sometimes, it's just not enough - sometimes components have methods and variables on them that need to be called or bound to respectively by the parent component (e.g. the Vuetify Calendar component). Or maybe the component is outside of your scope of control and they made a poor decision in that regard. Regardless, if the component in question does not exist when the parent is mounted (e.g. due to a v-if on the component), then any references to some state on it through I ran into this case recently and since I didn't see it elsewhere in this thread, I thought I'd post about $forceUpdate here: https://vuejs.org/v2/api/#vm-forceUpdate What I was able to do was hook on the event that caused my component to mount and then in $nextTick, I called $forceUpdate in the parent. This caused the parent to rerender which then fixed my stale UPDATE: One weakness to this approach is that it forces the component to render, but it doesn't force computeds to recompute. So, if you have a reference in your template via $refs, it will work. If you are relying on a reference in a computed to $refs it will not. However, I did find this little gem which shows you how to force a computed to update if needed: #214 (comment)
|
|
It seems like a bug that even when adding
https://jsfiddle.net/cytduw8k/ Is this due to non-reactivity in |
Now that refs are no longer reactive, we need to emit events and payloads from child components.
This is fine and works well, but it becomes hard to maintain in the following scenario:
When you create a custom form input component, that basically wraps an input into it's own re-usable component, we can no longer access the input's value (and other props) reactively in e.g. a computed prop in the parent.
Take the following use-case:
Previously we could create a computed prop like this:
Since refs don't work reactively anymore, we now need to use
$emit
to inform the parent of changes.However, these emits are handled by methods, so we need a separate handler per input to deal with the events.
Something like:
You could refactor this a bit, but it's not a nice way of dealing with this and I'm assuming using forms is quite a common occurence for a lot of devs.
Can we think about a better solution to something like this?
The text was updated successfully, but these errors were encountered: