-
Notifications
You must be signed in to change notification settings - Fork 13
Tunnel per instance? #8
Comments
@jthoms1 Came across this issue as well. Would like to have slotted child components to know the state of a parent component. But, as mentioned above, I cannot have different props for different instances, one will override the other. Making it not useful in component libraries. Any input on if it is possible to implement this? |
Agreed, it would be extremely helpful to allow multiple instances of the same type of tunnel exposing different values. Ideally I should be able to nest them. <my-tunnel-provider value="foo">
<my-tunnel-consumer />
<my-tunnel-provider value="bar">
<my-tunnel-consumer />
</my-tunnel-provider>
</my-tunnel-provider> |
Does anyone have some example code that they can share? |
@jthoms1 my example is pretty far from minimal but I can make some time to produce a simple example if nobody else beats me to it |
@jthoms1 I put up a repo that demonstrates the issue https://github.com/sslotsky/stencil-highlander Both instances of tunnel definitionimport { createProviderConsumer } from "@stencil/state-tunnel";
export interface State {
message: string;
}
export default createProviderConsumer<State>(
{
message: ""
},
(subscribe, child) => (
<context-consumer subscribe={subscribe} renderer={child} />
)
); providerimport { Component, Prop } from "@stencil/core";
import Tunnel from "../../data/tunnel";
@Component({ tag: "my-tunnel-provider" })
export class MyTunnelProvider {
@Prop() message: string;
render() {
return (
<Tunnel.Provider state={{ message: this.message }}>
<slot />
</Tunnel.Provider>
);
}
} consumerimport { Component } from "@stencil/core";
import Tunnel, { State } from "../../data/tunnel";
@Component({ tag: "my-tunnel-consumer" })
export class MyTunnelConsumer {
render() {
return (
<Tunnel.Consumer>
{(state: State) => <h1>{state.message}</h1>}
</Tunnel.Consumer>
);
}
} usage <my-tunnel-provider message="Foo">
<my-tunnel-consumer />
<my-tunnel-provider message="Bar">
<my-tunnel-consumer />
</my-tunnel-provider>
</my-tunnel-provider> result |
Tunnel's created in this way are using the fact that the module is used as a global so anyone can import from it and have access to the exact same data. One way around this might be to create it in a way that you are using a 'key' to differentiate between the different states. The usage provided is much more dependent on the structure of your application. I understand the issue but could you provide a more 'real world' example of when this would be helpful. Thank you for your time on this! |
First I'll offer a generic answer, which is that this is the way people coming from React will expect this to work, because tunnels are based on React context, and React context works this way. It's likely that there are many real world examples out in the wild. As for my specific use case, my team is building a component library that talks to our API. I have a docs page that shows many of these components in use, and I wanted some of them to show data from production and I wanted others to show data from staging. Example: <connection env="prod">
<marketplace />
<product label="some-product-label" />
<connection env="stage">
<plan-selector product-id="2349823fadf234afee" />
</connection>
</connection> Our API is microservices, so by putting Of course they don't strictly have to be nested, but placing the |
I think this is valid. The more I think about it. There are other reasons why someone might want this. I think I have some ideas on solving this and keeping it backward compatible. I will on a PR. |
Just to add on this, so you can see my use case: <wc-accordion some-prop="some-value">
<wc-accordion-item>My accordion item 1</wc-accordion-item>
<wc-accordion-item>My accordion item 2</wc-accordion-item>
<wc-accordion-item>My accordion item 3</wc-accordion-item>
</wc-accordion> In my case, I am watching this issue so that I could pass a single bit of information on the parent component (wc-accordion) that all of the children components (wc-accordion-item) could read from (through a slot) and the child components could use the prop on the parent component as a default value, but if the children components have a specified value it would override that parent value, if that makes any sense. Basically, and in general, it would allow components that are built with each other in mind, can sync up a lot better especially through slots. |
I think we have the need in Ionic as well. This will be priority for the next release.
|
We have the same issue, in our case we are doing a web component for Masorny JavaScript lib, so at the end we have this
At the end in the So in this case you can have only ONE masorny component loaded per page, which would be Ok for our use case but probably not for everyone. thx p.s. are there news about this problem?!?! thx a lot |
Is there any plan or progress with this feature? It would really help in quite some use cases for component collections. |
@jthoms1 Let me know if there's any way I could help get the ball rolling on this. Would really love to see this added in time for Stencil One to come out of beta! If it's not too incredibly hard and you can suggest where to start looking, I'd be open to taking a shot at it. |
Hi, I have a strange behavior that contradicts problems stated here. Namely, I have a top component with render function:
As you can see, I am sending as a state an instance of component. Consumer (a component inside top component, an inner component) consumes this state like this:
I have two those components on the page (carousel is top component, provider, pager is nested component that consumes state):
For some reason which is unknown to me - this sh*** works as expected, every pager has access only to parent component instance as state. Why? Beats me... |
I have spent some time thinking about this issue and in general about problem of shared stated among composed/composition of components within some context, as well on application level. In that matter, I have wrote a small library as proof of concept, where components can share a state store. Solution is simplified version of NGXS, and uses RXJS as implementation of observable pattern. My biggest issue was that order of invocation of component lifecycle method is not guarantied, I have experienced that order can vary, sometimes, child components are rendered first, sometimes, parent components - without any code modification. So I had to introduce a global registry of providers to solve this problem. However, because of that, there is a neat feature which allows to the user to dynamically add subcomponents/consumers in runtime, as well as parent components/providers. There is a small demo as well, provided with this library: https://github.com/RunOpenCode/stencil-state-store EDIT: here is a demo video: https://youtu.be/D07vAxlEUS0 |
Is there any progress on this? |
I tried nesting tunnels, as I wanted to override values in tunnel, lower down the component tree. This ends up causes an infinite loop of re-rendering. (Maybe this should be a separate issue, I'm not sure). Demo repo: https://github.com/petermikitsh/stencil-nested-tunnel render() {
const context = {foo: 'Test'};
return (
<div>
<Tunnel.Provider state={context}>
<Tunnel.Consumer>
{(context: TunnelContext) => {
const newContext = {...context, foo: 'foo'};
return (
<Tunnel.Provider state={newContext}>
<Tunnel.Consumer>
{(context: TunnelContext) => {
return <div>{context.foo}</div>
}}
</Tunnel.Consumer>
</Tunnel.Provider>
);
}}
</Tunnel.Consumer>
</Tunnel.Provider>
<Tunnel.Provider state={context}>
<Tunnel.Consumer>
{(context: TunnelContext) => {
const newContext = {...context, foo: 'bar'};
return (
<Tunnel.Provider state={newContext}>
<Tunnel.Consumer>
{(context: TunnelContext) => {
return <div>{context.foo}</div>
}}
</Tunnel.Consumer>
</Tunnel.Provider>
);
}}
</Tunnel.Consumer>
</Tunnel.Provider>
</div>
);
} Browser: |
I took a stab at implementing nested context overriding here: https://github.com/petermikitsh/stencil-context It's published on npm as |
Are there any updates on this issue? I feel like this is a necessity for anyone hoping to build a collection of components with parent-child relationships where children are passed in by consumers as |
When building component library's the ability to have multiple parent components interact with the children via tunnel would be very useful. A "real world use case" could be as simple as creating a
In the above pseudo code example, having the first line only interact with the immediate children is obviously necessary and beneficial. Is this many fixed in the latest version of Stencil? |
Very cool. Will take a look. Will you be updating this along with stencil updates? |
@jthoms1 Any update on this? My team had to go away from using the tunnel because of this. We'd love to help make this happen but are under a time crunch right now |
Hey @sslotsky |
Our implementation of components composition and shared state is now stable: https://github.com/RunOpenCode/stencil-state-store, we dropped state tunnel concept. Documentation is updated, after some trial period of usage, we figure out what public API should be so we released version 1.0. Here is the real-world example of its usage: https://www.miross.rs/en - all carousels are composed web components sharing state. |
@petermikitsh excellent library! https://blog.mikit.sh/post/Stencil-Context/ I like the use of events for propagating the request to subscribe to context. |
Landed in the same boat a few days ago. I forked and updated the project to use instances instead of a static, globally shared tunnel. Works, but introduces breaking changes. Will share it soon. |
Hey guys I've created a solution to passing props down component trees that's instance scoped. Feel free to check it out -> https://github.com/mihar-22/stencil-wormhole. |
I'm working on a standard event contract to unify the efforts of the authors of libraries here and in the polymer community. Here is a summary of the libraries and how they work: https://github.com/saasquatch/dom-context/tree/v1 |
According to the examples,
createProviderConsumer()
is created statically in a file, e.g.data-tunnel.tsx
. This basically means tunnels are created on class level rather than on instance level. While this is totally the way to go for single page apps that only have one instance of a root component, e.g.my-app
, it makes it hard when building a component library, where every component instance should have its own tunnel and multiple instances of that component can coexist. In other words:Current - these two instances of "my-select" now share the same tunnel and mix up the state. This leads to unexpected behavior:
Expected - it would be great if the developer could decide whether to have tunnels on instance level or on class level. Every time an instance of "my-select" is created, also a corresponding tunnel is created. So each parent has its own tunnel and its child components automatically consume that particular tunnel:
The text was updated successfully, but these errors were encountered: