Skip to content
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

Callback refs as additional alternative to "named" refs #5987

Open
mcjazzyfunky opened this issue Jun 28, 2017 · 10 comments
Open

Callback refs as additional alternative to "named" refs #5987

mcjazzyfunky opened this issue Jun 28, 2017 · 10 comments

Comments

@mcjazzyfunky
Copy link

What problem does this feature solve?

Currently if you want to use refs to elements (DOM elements or Vue components - see attribute/prop "ref") you have to provide a string as "ref" attribute. A callback function as "ref" attribute like in React is currently not supported in Vue.
It would be great if also callback functions could be provided as "ref" attributes (especially when doing a bit more advanced stuff using the "render" function).

The callback function should be called both when the referred element is created and also when it is disposed (React for example passes null in the latter case).

It seems that this feature had already been implemented in the past but has been reverted later (I do not know for what reasons the changes have been reverted) => see: "[WIP] Support for ref callback #4807"

Thank you very much.

What does the proposed API look like?

Please see line 178 here:
90c6c29

const myRefCallback(ref, remove) {...} (where "remove" is boolean) seems to me like a better solution that the one that is used in React where in the "remove" case the ref callback function is just called with null.

@sqal
Copy link
Contributor

sqal commented Jun 28, 2017

FYI this feature has been dropped at the time because of this bug #4998

@no1xsyzy
Copy link

no1xsyzy commented Jul 6, 2017

I am not very sure what you preferred, but i think v-bind:ref could help with most of the situations. Although using :ref with v-for will still make the $refs be Arrays and disposing a components will not destroy the Array but destroy the components inside (may result in empty array/s).
Take a look at this example: https://jsfiddle.net/no1xsyzy/036scaat/4/, pay attention to the console output.

@mcjazzyfunky
Copy link
Author

Thanks a lot, no1xsyzy, for that information and your demo.
But the problem is a bit deeper:
'String refs' are fine - I normally use them as-is in most cases.
But 'callback refs' are far more powerful.
Especially for those cases where you do not use templates (let's say you develop some kind of extension library for Vue) the lack of 'callback refs' may really be a show stopper.

That's why Facebook/React have switched from 'string refs' to 'callback ref':
https://facebook.github.io/react/docs/refs-and-the-dom.html#legacy-api-string-refs

Moreover in Vue it's not possible to use refs in templates for functional components (as that would require callback refs).
In React that's possible:
https://jsfiddle.net/ozjdyr9L

In some cases and only for non-functional component it may be possible to simulate the behavior of callback refs by using unique generated ref names and some additional logic in certain lifecycle methods - but that would be really ugly, in some cases slow and not that powerful.

The feature to allow 'ref callback' would open Vue some really interessting new possibilites.

@mcjazzyfunky
Copy link
Author

mcjazzyfunky commented Feb 8, 2018

I changed my mind regarding the above suggested signature of the callback function:
I think it would be better to make the callback signature somehow compatible with React to make it easy for wrapper or whatsoever libraries that provide something that could be used for both React and Vue.

Instead of
function myRefCallback(ref, remove) {...}

I would now suggest:
function myRefCallback(ref, prevRef) {...}

Means "myRefCallback" will either be called:
myRefCallback(ref, null) (on rendering)
or
myRefCallback(null, ref) (on removing)

React does not have that second argument in the ref callbacks, but the first argument would behave exactly like in React.

@decademoon
Copy link
Contributor

I'm also interested in Vue implementing callback refs. They're so much more powerful in my opinion.

In my situation, as a workaround, I've resorted to storing a reference to the vnode whose component instance I want a ref for, then delegating to vnode.componentInstance to get the ref at a later time.

Unfortunately I have no way of knowing when the instance has been created/mounted, because until then vnode.componentInstance is undefined. I'm pulling the instance instead of relying on a push from the framework.

@stalniy
Copy link

stalniy commented Jul 18, 2018

Frankly speaking I don't think that it's a good idea. Function as a ref allows to do crazy side effect stuff. Moreover, you can create internal state using closures and you will recreate this internal state each time your render function is called. Render function of functional components are called together with parent's render function

So, eventually it may have performance issues

@tangjinzhou
Copy link

Maybe we can use directive to implement ref callback function.
I published a plugin vue-ref, you can try it.
I used it in ant-design-vue and found no problems for the time being.

@jumika
Copy link

jumika commented Oct 6, 2020

I really don't know why is this issue is neglected.

@bryanmylee
Copy link

Frankly speaking I don't think that it's a good idea. Function as a ref allows to do crazy side effect stuff. Moreover, you can create internal state using closures and you will recreate this internal state each time your render function is called. Render function of functional components are called together with parent's render function

So, eventually it may have performance issues

That's a pretty disingenuous argument. Just because you can do bad things with the feature doesn't mean the feature is bad. You could easily create infinite update cycles with get/set refs, but that doesn't mean we don't support it.

In addition, "crazy side effect stuff" over-generalizes an entire class of necessary features. The whole point of UI libraries is to manage side effects, otherwise what's the point of building UIs? It's the reason why we need watchEffect in the first place.

@bryanmylee
Copy link

I'm building a library for Vue 3 and this is one of the limiting factors. I have controlled components that can receive an as-child boolean prop which causes the component to render as a fragment and "pass" the required props to its children via v-slot.

<Dialog.Content>
  ...
</Dialog.Content>
<!-- becomes -->
<div id="ally-0-content" role="dialog" aria-modal="true" aria-labelledby="ally-0-title" aria-describedby="ally-0-description" data-state="open">
  ...
</div>


<Dialog.Content as-child v-slot="props">
  <section v-bind="props">
    ...
  </section>  
</Dialog.Content>
<!-- becomes -->
<section id="ally-0-content" role="dialog" aria-modal="true" aria-labelledby="ally-0-title" aria-describedby="ally-0-description" data-state="open">
  ...
</section>

However, the controlled component sometimes needs a reference to the DOM node to implement stuff like focus trapping, custom event management, layout measurements etc. If rendering as a fragment, there's no official way to get the DOM node that's supposed to receive the props, but a callback ref passed via v-slot would work perfectly.

Workarounds

Surprisingly, when I simply pass the callback ref as a property of the slot props, the callback ref works as expected.

While this solves my issue, I'm not sure if this is intended behaviour or if it will be removed in the future by a fix.

<!-- DialogContent -->
<script setup lang="ts">
const setRef = (node: HTMLElement | null) => {
  ...
};
</script>

<template>
  <slot v-bind="{ref: setRef}"
</template>

<!-- App -->
<Dialog.Content as-child v-slot="props">
  <section v-bind="props"> <!-- the callback ref is correctly triggered here -->
    ...
  </section>  
</Dialog.Content>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants