sveltejs / svelte

web development for the rest of us
https://svelte.dev
MIT License
80.25k stars 4.27k forks source link

Get self instance from inside of a component #14163

Open Freeskier opened 3 weeks ago

Freeskier commented 3 weeks ago

Describe the problem

In my project I needed a self instance of the component itself. What I mean by that I will provide in an example below. From the docs: App.svelte

<ShoppingCart bind:this={cart} />

<button onclick={() => cart.empty()}> Empty shopping cart </button>

ShoppingCart.svelte

<script>
    // All instance exports are available on the instance object
    export function empty() {
        // ...
    }
</script>

This is a valid code and you can store the reference of ShoppingCart in the parent component.

In my scenario, I needed to pass the same cart reference, but from inside of the ShoppingCart. Of course there is a workaround by storing and passing the reference as a props to the child component like below: App.svelte

<ShoppingCart bind:this={cart}  ref={cart}/>

ShoppingCart.svelte

<script>
    let { ref } = $props()
       *** now I have access to ref***
    export function empty() {
        // ...
    }
</script>

Describe the proposed solution

I'm not that deep into the fundametals of the svelte, thats why I don't know if my proposition has any sense. But what about consider $self() rune?

Importance

would make my life easier

paoloricciuti commented 3 weeks ago

Can't you just call empty directly from within the component?

Freeskier commented 3 weeks ago

@paoloricciuti it's just a quick demo to show you what I meant. In my real scenario, from the child component on the specific action I need to pass the reference. A very short example below: store.svelte.js

let refs = $state([]);

export function createStore() {
  return {
    get refs() {
       return refs;
    },
    addRef: (ref) => refs.push(ref);
}

Component.svelte

<script>
  const store = createStore()

  export function foo() {
    alert('foo');
  }
</script>

<button onclick:{()=> store.addRef(***HERE I NEED REFERENCE***) }>clickme</button>

AnotherComponent.svelte <- This one exist somewhere, its not the direct parent of the Component.svelte

<script>
// on some action I want to be able to call empty() function from refs
const store = createStore()

store.refs.forEach(r => r.empty())
</script>
Leonidaz commented 3 weeks ago

I think it would be easier to have each component add its own empty function to the list of refs when they're instantiated.

Probably a module level scope that is available to all instances of a component via <script module> https://svelte.dev/docs/svelte/svelte-files#script-module might be the most pretty straightforward way of doing it but yeah, you can also import an external store in the module or in the instance.

Here's a quick demo that I created. I also added a on-destroy effect to remove the empty ref from the "store".

Quick Demo

brunnerh commented 3 weeks ago

If the functionality is more complex than just a function, you end up essentially rebuilding the instance's API by hand. I also could have used a functionality like this when orchestrating between complex components via a context; had to wrap a bunch of properties in accessors and pass all relevant functions manually.

Would vote for a $this or getCurrentComponent() function (which existed before in a different form as part of the internal APIs).

Freeskier commented 3 weeks ago

@brunnerh yeah, $this makes sense, because it refers to bind:this. @Leonidaz Thank you for the demo, of course there are workarounds as you presented. I just mentioned about this feature here just because it'd make life easier :)

trueadm commented 3 weeks ago

Things like $this and getCurrentComponent are complicated to implement in Svelte 5. That's because components aren't instances and are instead functions, so when you reference them, you're actually just taking the object they return when called. As such, I don't think there's an elegant way of implementing such things without incurring overhead for users that don't use these features. Not to mention that we probably want to avoid adding any more runes right now for things that have work-arounds, even they are cumbersome.

paoloricciuti commented 3 weeks ago

Things like $this and getCurrentComponent are complicated to implement in Svelte 5. That's because components aren't instances and are instead functions, so when you reference them, you're actually just taking the object they return when called. As such, I don't think there's an elegant way of implementing such things without incurring overhead for users that don't use these features. Not to mention that we probably want to avoid adding any more runes right now for things that have work-arounds, even they are cumbersome.

I wonder if we could add an exports variable in the compiler code, we add stuff to that variable and then we return that variable and when people call $this() we just access that variable. But that's just a random thought

trueadm commented 3 weeks ago

@paoloricciuti Maybe, but it gets complicated around components that have accessors enabled as they're added as getters/setters.

Freeskier commented 3 weeks ago

Things like $this and getCurrentComponent are complicated to implement in Svelte 5. That's because components aren't instances and are instead functions, so when you reference them, you're actually just taking the object they return when called. As such, I don't think there's an elegant way of implementing such things without incurring overhead for users that don't use these features. Not to mention that we probably want to avoid adding any more runes right now for things that have work-arounds, even they are cumbersome.

@trueadm I'm not sure about my idea (i tried to take a look into svelte lib but its quite hard), but if components are in fact functions, can't it be done by taking a reference of it and pass down as a "hidden" prop that can be accessed?

trueadm commented 3 weeks ago

@Freeskier bind:this uses to the object that is returned from the component function. If you look on the playground at the compiled output, it should make more sense. That's how it's got to work, we can't change that mechanic without breaking everyones app that uses bind:this on components today.