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

Cannot bind to stores from within a snippet #13076

Open
pauldemarco opened this issue Aug 30, 2024 · 16 comments
Open

Cannot bind to stores from within a snippet #13076

pauldemarco opened this issue Aug 30, 2024 · 16 comments

Comments

@pauldemarco
Copy link

Describe the bug

I am getting this error when trying to access a store passed in via snippet parameter:

Cannot subscribe to stores that are not declared at the top level of the component

Reproduction

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAAA42QS2rDMBCGrzKoBdtg7K6T2PRxjLqLxJ4QEVkSo3FLEbp7keQ40G661Oh_zHxenKVCJ3bvXujjjGInXqwVteBvGx_uExWjqIUzC41xcnAjScv9oAeWszXE8LowGw1nMjMUTZufTbYW-0Ef2rtHH1a10aOS47XzZQVdD0eFxGXxFmc4AV8QTklYVCF1-QenpbXIMF6kmgh1acnYKsTPgVMY-Mc4SyPfrvoQF8ilvajFbCZ5ljiJHdOCod7uzpL_nq6QwW-r1NA0Tax2EKCDtIUrq_2gV0Qevkjy8aQQwgoq57eODSVKt9TRLJqRoNss5VP1G2JmA_5WuzJ6JtQT0h3RGlYlCNnUD_ovho_wA4i41NgKAgAA

Logs

No response

System Info

System:
    OS: Linux 6.8 Ubuntu 22.04.4 LTS 22.04.4 LTS (Jammy Jellyfish)
    CPU: (24) x64 12th Gen Intel(R) Core(TM) i9-12900K
    Memory: 48.88 GB / 62.54 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 22.6.0 - ~/.nvm/versions/node/v22.6.0/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 10.8.2 - ~/.nvm/versions/node/v22.6.0/bin/npm
    pnpm: 9.1.4 - ~/.local/share/pnpm/pnpm
    bun: 1.0.0 - /usr/local/bin/bun
  Browsers:
    Brave Browser: 110.1.48.171
    Chrome: 128.0.6613.84
  npmPackages:
    svelte: ^5.0.0-next.241 => 5.0.0-next.241

Severity

blocking an upgrade

@trueadm trueadm added this to the 5.0 milestone Aug 30, 2024
@trueadm
Copy link
Contributor

trueadm commented Aug 30, 2024

Rather than passing arounds store, you should be passing in the value from the store into the snippet. This is because we can't create subscriptions inside the snippet – which is required when you reference a store with $ prefix.

@pauldemarco
Copy link
Author

pauldemarco commented Aug 30, 2024

Rather than passing arounds store, you should be passing in the value from the store into the snippet. This is because we can't create subscriptions inside the snippet – which is required when you reference a store with $ prefix.

This might be a problem since the component that is calling the snippet is generic and only knows that it is a store and unaware of the shape of the data.

I am recreating Superforms "componentization": https://superforms.rocks/components

I have a component called SimpleForm that acts as a wrapper to handle all the shared superform logic and must be passed the inner input elements through the children slot. The issue is that these inner elemenets need to bind to the different properties of the superform $form data (which is created and only available in SimpleForm).

@trueadm
Copy link
Contributor

trueadm commented Aug 30, 2024

Rather than passing arounds store, you should be passing in the value from the store into the snippet. This is because we can't create subscriptions inside the snippet – which is required when you reference a store with $ prefix.

This might be a problem since the component that is calling the snippet is generic and only knows that it is a store and unaware of the shape of the data.

I am recreating Superforms "componentization": https://superforms.rocks/components

I have a component called SimpleForm that acts as a wrapper to handle all the shared superform logic and must be passed the inner input elements through the children slot. The issue is that these inner elemenets need to bind to the different properties of the superform $form data (which is created and only available in SimpleForm).

I mean this is already the case with Svelte 4 slots, you cannot subscribe to a store inside the slot either. How did you do this before with Svelte 4?

@pauldemarco
Copy link
Author

pauldemarco commented Aug 30, 2024

This is true. After I made this issue I attempted to do this in Svelte 4 and reached the same issue.
The solution presented by Superforms is to make another component TextField that actually does the subscribing at the top level.
I left this open to hopefully learn about why this isn't possible, and if it maybe can be done without this workaround using snippets.

Side note: I attempted to convert the $form store within SimpleForm to a rune with fromStore and pass that instead, but it seems to lack deep reactivity since I cannot bind to the nested data, e.g. <input bind:value={formRune.current.firstName} />

@trueadm
Copy link
Contributor

trueadm commented Aug 30, 2024

fromStore only converts the subscription into a signal, it doesn't apply deep state to them – by design.

@pauldemarco
Copy link
Author

fromStore only converts the subscription into a signal, it doesn't apply deep state to them – by design.

Interesting, it seems the only solution then is to pass the superform through the snippet --> into an inner component, and subscribe from there.
It would be incredibly helpful, (and may be a great test of the API), if this superforms componentization project was converted to Svelte 5:
https://www.sveltelab.dev/?provider=github&owner=ciscoheat&repo=superforms-examples&branch=components-zod

@trueadm
Copy link
Contributor

trueadm commented Aug 30, 2024

What about doing this?

@pauldemarco
Copy link
Author

pauldemarco commented Aug 30, 2024

What about doing this?

It almost worked! But broke once I tried to target a nested property: Try here

@trueadm
Copy link
Contributor

trueadm commented Aug 30, 2024

What about doing this?

It almost worked! But broke once I tried to target a nested property: Try here

That's because the nested property isn't reactive? You need to use $state to make it deeply reactive.

@pauldemarco
Copy link
Author

pauldemarco commented Aug 30, 2024

Yep, by design, as you stated.

What is the reason for this difference?

// title is reactive
let data = $state({title: “hello”})

vs.

let dataStore = writable({title: “hello”})

// title is NOT reactive
let data = fromStore(dataStore)

@trueadm
Copy link
Contributor

trueadm commented Aug 30, 2024

@pauldemarco I mean you can put state into a store, that's what I was demonstrating – to make the store deeply reactive.

@pauldemarco
Copy link
Author

The goal at this point is to convert it into a deeply reactive rune so that I could bind to it within the snippet.

@trueadm
Copy link
Contributor

trueadm commented Aug 30, 2024

@pauldemarco I get that, but if you convert it into a deeply reactive state, it won't propagate changes back up to the store again. The store itself needs to define this otherwise the deep state will only be local.

@pauldemarco
Copy link
Author

pauldemarco commented Aug 30, 2024

@pauldemarco I get that, but if you convert it into a deeply reactive state, it won't propagate changes back up to the store again. The store itself needs to define this otherwise the deep state will only be local.

I don’t have the ability to create the store — it is given to me by the Superform library.

The hope was to convert into a rune that is completely 2-way bound to the store. It seems though this isn’t possible, ‘fromStore’ creates a rune with only top level reactivity, and does not propagate inner changes back to the store.

I guess I would ask, is it not possible to have it just be fully reactive and stays completely in sync with the store? I can’t imagine, especially during the transition period from 4-5, that this wouldn’t be the intended operation everyone expects.

@trueadm
Copy link
Contributor

trueadm commented Aug 30, 2024

@pauldemarco You'll need to rework things or get the upstream library to move over to Svelte 5.

@webJose
Copy link
Contributor

webJose commented Aug 30, 2024

Naive question for @trueadm or any other Svelte master: Why the scope cannot be determined in the template? Isn't the template a valid place where to subscribe to stores? I wonder this because $effect.tracking() returns true when rendering the template. When we do classes that handle subscriptions we use $effect.tracking() to run an $effect(). This subscribes, say, regular classes to the calling component's lifecycle. At least this is my understanding. I could be complete wrong, therefore my asking.

See this example in the runed project.

@trueadm trueadm removed this from the 5.0 milestone Aug 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants