-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Passing values from slot to parent #3617
Comments
Hi, The part of the documentation you mention doesn't say what you interpreted. Binding values is different from exposing a property from the slot to the parent. It will be helpful to said what are slot are and use for. Slots are just placeholder:
The slot tag will be "replace" with the html markup that you put inside your Component tags. A use case can be... Let's said you want to create a DataTable component, on the DataTable component you create all your table html markup, style and javascript. So to use the component i just need to pass a dataset, where this dataset is an array of objects. How you display each one of those items and in which order? You don't know, because when you created the component you din't have the data and you what to use this component for different type of objects. So instead of creating complex configuration for the component you just expose each object (item) of the dataset to the parent. So here you will use a slot, so future developers and you can said how the markup of a row will be, and with the expose item you have the data of your dataset that you pass to your component available. So svelte documentation saids:
Continuing the documentation after the previous line it saids:
This means that to expose a property from the slot child to the parent you need to do:
And to use that property on the parent you need to do:
Check the svelte example on this part of the documentation for better understading. If you don't do that, the item on the parent will be undefined. Thus not having access to the data you which. On the other hand... Binding In a nutshell, the purpose of this is to update the variable programmatically or when the DOM element/components updates it. So if you have an input that his value is bind to a variable X when the input change it will change the value of the variable X but also if you change the value of X programmatically it will also change on the input and any place you reference the variable X. From svelte tutorials we can bind to components properties too:
But to components props, and to create a prop you need to use the export keyword. More on component bidings. The extended explanation is in case you miss something and for future devs that find this issue, have a better understanding of slot and binding, and more if they're beginners to Svelte. From your REPL I edit it a little, maybe this was what you were trying to do.. It binds to the components and also expose the value to which it binds. Making it posible to update the given value from inside the component but also from outside the component while also exposing the value from the slot to the parent. Or, are you asking for the functionality that one can specified future bidings to the slot? |
Hi, I came across a similar/relevant issue where I'm wanting to use slot props to pass a value to a parent component so that it can be bound to a specific element in the slot. This would allow the component containing the slot to access the DOM element in For example, <Field let:input={input}>
<label>X</label>
<input bind:this={input}/> <!-- Cannot bind to a variable declared with the let: directive -->
</Field> Here's a REPL that illustrates the same issue: https://svelte.dev/repl/94dfa039dfd645ad8265609377873457?version=3.20.1 My intention is for the I've tried passing a variable Is it possible for Svelte to support this type of binding? |
I would love to see this features as well, since it makes component composition and reuse a lot easier. |
I also wanted to use this in a way to provide a context for child components, where the parent component contains a bunch of boilerplate that the child components shouldn't have to worry about, and the parent provides two-way bindable variables that children can take as props. Slots seem like these magical things that easily break when you play around with them too aggressively, I guess the benefits of a compiler don't matter when Svelte treats slots as though literally anything might fill them. |
I happened upon this issue while investigating a slightly different usage. May I ask why it isn't possible to do the following. Am I misunderstanding the question? I get that the <!-- App.svelte -->
<script>
import Component from './Component.svelte';
let item = 'hello, world';
</script>
<Component {item}>
<p>
Inside the item is {JSON.stringify(item)}
</p>
<input type="text" name="name" bind:value={item} />
</Component> <!-- Component.svelte -->
<script>
export let item;
</script>
<p>
Outside the item is {JSON.stringify(item)}
</p>
<slot /> |
@kjmph Seems to be working in the REPL: https://svelte.dev/repl/72d0ab2799a04343bc8054176ee4208d?version=3.25.0 |
Thanks for placing it in the REPL. It seems that the |
@kjmph Well that won't work since you can't redeclare |
Ahah, I think you showed the problem. If we change Component.svelte to: <script>
export let item;
</script>
<p>
Outside the item is {JSON.stringify(item)}
</p>
<slot foo={item+"nana"} /> Then the output is: Instead, if we really want two way mutation between parent and child, then it is best to represent it like this: <!-- App.svelte -->
<script>
import {item} from './stores.js';
import Component from "./Component.svelte";
$item = 'hello, worl';
</script>
<Component>
<p>
Inside the item is {JSON.stringify($item)}
</p>
<input type="text" name="name" bind:value={$item} />
</Component> <!-- Component.svelte -->
<script>
import {item} from './stores.js';
$: $item = $item.replace('world', 'cow');
</script>
<p>
Outside the item is {JSON.stringify($item)}
</p>
<slot /> // stores.js
import {writable} from 'svelte/store';
export const item = writable('hello, world'); Just type the letter |
This question subtly brought up a few points about Svelte I didn't understand. Which is why I have commented and looked into it at a greater depth. @trenta3, your link to show the problem results in a <!-- App.svelte -->
<script>
import Component from "./Component.svelte";
let items = ["foo", "bar", "mung"];
</script>
{#each items as item}
<Component bind:item>
<p>
Inside the item is {JSON.stringify(item)}
</p>
<input type="text" name="name" bind:value={item} />
</Component>
{/each} <!-- Component.svelte -->
<script>
export let item;
</script>
<p>
Outside the item is {JSON.stringify(item)}
</p>
<slot {item} /> As well, people also want to have references go back to the component. However, at the time the component is mounted, the slot isn't mounted. So, there would have to be a callback to the component when the slot is mounted. Something like this: <!-- App.svelte -->
<script>
import Component from "./Component.svelte";
import { onMount } from "svelte";
let input;
let comp;
onMount(() => {
comp.onSlotMount(input);
});
</script>
<Component bind:input bind:this={comp}>
<input bind:this={input} />
</Component>
<!-- Component.svelte -->
<script>
export let input = undefined;
export function onSlotMount(input) {
input.value = 'insert value';
}
</script>
<slot {input} /> It all has to do with how binding works. It is very non-intuitive, yet makes sense once grokked. |
@kjmph I can see my image correctly when visualizing it in GitHub: https://user-images.githubusercontent.com/10828817/71427308-93471000-26b7-11ea-84fd-b5b1a970fd5a.png |
Ah, I was referencing the REPL link that was shared. If you would like to re-post that, maybe we could coerce a solution to your liking. I think the point I've been circling, and why I got involved on the issue thread is that the Thus, with that understanding, it should be hopefully clearer that the item needs to have a location/variable that the compiler can see in the App for the slot to bind to, so that the data flows reactively. Thus, the storage should be somewhere the compiler can see it, either through a Technically, in my example above, this means that when mounting the slot in the Component, e.g. Now, I'm not a Svelte dev, so I'm sure I misused some of that terminology. Does this help? |
I did some experiments:
Code: So, if I pass store (slot prop) as prop of an inner component, it works. I think the issue is that the compiler is unwilling to use store not defined at top level, despite it can compile the code successfully. |
Hello @locriacyber, Yes, as I attempted to address with my fumbling paragraph: Your example is still passing a reference to the writable instance through to the slot, and out again to the Inner component. The Inner component needs to be written in a way that knows to subscribe to updates to the storable. i.e. with the dollar sign. Although, I will comment, the passing of the reference as a prop of an inner component gets around the "Stores must be declared at the top level of the component (this may change in a future version of Svelte)" error. So, that is pretty interesting. I never heard back from @trenta3, so I'm guessing what his usage is, but the fundamental problem that flows oddly for people is that slots get a copy of the variable when it is a discrete type. They can edit that copy, and would only see local changes. Thus, this is why the compiler won't allow us to bind variables that have been declared with a let. Now, if the compiler subtly noticed that someone wants to bind to a let, and added an instance of a storable behind the scenes, maybe that would work. Haha, silly. |
@locriacyber, if you want a laugh, change your App.svelte component to: <script>
import DataContainer from './DataContainer.svelte'
import Inner from './Inner.svelte'
export let texty;
</script>
<div>
<DataContainer let:text={text}>
<Inner text={text}/>
<p>App Truth: {text.subscribe(t => texty=t)&&texty}</p>
</DataContainer>
</div>
<style>
div {
border: solid 2px black;
}
</style> I think that goes a long way to show why slots behave contrary to developer expectations. |
Seems like we need slots to be compiled as anonymous components to fix slot-related issues like #5720, but then I don't know about which syntax to use. |
Can you explain how an anonymous component would fix this? The contents of the parent inside the slotted area is treated as a component that is passed to the child component, and the child component is responsible for copying the parent's slotted component into the slot. Thus, the compiler has restrictions on what it can do when generating the parent slotted component.. |
The |
Now this is a real show stopper, we need to address this asap... |
@JacobZwang I agree, this feels more like a missing feature than an intentional limitation. Perhaps it's just an honest oversight 😅 I was struggling with this for a while. I was trying to build a form component that abstracts things like loading state, error state, dirty state, submit & reset buttons, etc. Binding to a slot variable felt like something that should intuitively work for the inputs that end up being children of the form. The solution/workaround you posted worked perfectly for me. For those curious, here's a working repl to demonstrate: |
Hello, I know I've clumsily tried explaining.. I'll try again. The let directive is a one way binding. See the corresponding Svelte code: svelte/src/compiler/compile/nodes/Binding.ts Lines 51 to 54 in b5aaa66
This is because when the component mounts, it doesn't see the slot until it is mounted later. In fact, the reason formState needs to be defined as an empty object, is otherwise the first time it executes it would be undefined and throw an error. Then, later when onMount is called in the Form component, it tries to rebind the formState. This will rewrite the formState variable in the App with the new formState, thus re-rendering the slot. This is why bind is required, the App's formState needs to be a variable that can be referenced/written. Let won't work because it is intentionally a one-way bind. Does this help? What you wrote is precisely correct @crowdozer; that's the only way to pass back from slot to parent to grandparent. |
Could the maintainers clarify, is the lack of support for two-way binding to variables declared with the let directive due to technical limitations in Svelte's current reactivity system, some design decision or something else? To me this seems like it would be a very useful feature, especially when designing reusable containers for forms and input elements. |
Please add this to new features list |
You can use a store |
Its not a bug and works exactly as described, however the more i am developing components the more it is starting to bother me. I have some workarounds in place, but they make the code far more confusing. I have added a REPL with a super simple example of how I would love it to work. The REPL is of course broken |
Curious thing, |
I dont think there is a good way to slove this problem rn. Store might help but a component's state should not be globle, store in a context also does not help because you will need to create another wrapper just to access to the context. The best solution I can think of rn is just event dispatcher |
I haven't tried with runes yet . I hope the compiler will allow us to bind to a state rune / signal which was received by a let: statement in the slot |
From the documentation of slots it seems it should be possible to bind values of a component to a slot:
but it seems that the real situation is different: this REPL triggers the error
Cannot bind to a variable declared with the let: directive (10:32)
.Expected behavior
Binding a variable in a slot, which is bound to a variable in the parent component, should work normally as it would if I manually substituted the slot content inside the container.
Severity
This underpins the possibility of developing a lot of components that take care of boilerplate code for my application, so in my case this effectively blocks my usage of Svelte for the project.
The text was updated successfully, but these errors were encountered: