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

Possible to access child props? #222

Closed
Myrdden opened this issue Aug 17, 2020 · 12 comments
Closed

Possible to access child props? #222

Myrdden opened this issue Aug 17, 2020 · 12 comments
Labels
question Further information is requested

Comments

@Myrdden
Copy link

Myrdden commented Aug 17, 2020

Hello again. Currently working on a prototype which is a port of some features out of a React app. There are a lot (honestly, most) of container components that organize or present stuff based on props of their children, for instance:

<TabBar>
  <Component tab='First'/>
  <Component tab='Second'/>
  // etc.
</TabBar>

Which lays out the headers for each tab based on the child's tab prop. In Solid, all components seem to be compiled away completely and as such there doesn't seem to be a way to access child props. Is there any other mechanism by which it's possible for a component to know about it's internal structure like this?

@ryansolid
Copy link
Member

Are the Component components specialty components or can they be anything in userland? The reason I ask is a component can return anything. So it could return data including the tab info and it's rendered children. And the parent could read the tab you pass back. However, if it was any component this sort of API would not be easy. I think wrappers are the only thing that makes sense.. Like:

<TabBar>
  <Tab order='First'>
    <MyComp1 />
  </Tab>
  <Tab order='Second'>
    <MyComp2 />
  </Tab>
</TabBar>

// define Tab as:
function Tab(props) {
  return props;
}

Now the props.children in <TabBar /> will be an array of props objects each with order and children which are the rendered children. From there you can iterate the list and render as you see fit.

@ryansolid ryansolid added the question Further information is requested label Aug 17, 2020
@Myrdden
Copy link
Author

Myrdden commented Aug 17, 2020

Hmm, that's a good idea. The original could accept any component, and most of the framework functioned on a similar approach, so I more or less need to rebuild the thing completely. My approach so far has been essentially the same, except with non-JSX wrapper functions and POJOs. Working with Solid is very interesting, because it looks so much like React, and yet is fundimentally so very different. Thanks for the ideas!

@Myrdden Myrdden closed this as completed Aug 17, 2020
@mduclehcm
Copy link

@ryansolid
I have the same problem. But I need more on that. In my case, I need parent component that organize children components. But I still need my children components can render individually by it self.

const group= (
<Parent>
  <Child/>
  <Child/>
</Parent>
);
const single = <Child/>;

@Myrdden
Copy link
Author

Myrdden commented Aug 18, 2020

@mduclehcm If the Parent doesn't need to know what it's children's props or structure is, that will work as is. If it does, you'll need to wrap it somehow. Something like:

<Parent>
  <With first='prop' second={prop}><Child/></With> // Where With = (props) => props; basically
  // or, you could do something like
  <Child first='prop' second={prop}  subordinate/>
</Parent>
// Where <Child> then has
if (props.subordinate) { return [props, <Child_Rendered_JSX/>]; }
else { return <Child_Rendered_JSX/>; }
// which then lets parent see props, whilst still letting it be used elsewhere. Problem with this is you'd have to
include the props.subordinate check on everything that could be used in Parent, which is no good.

Also, an idea I've been exploring is extracting the logic out of JSX and into a function, something like

createParent({...propsSpecificToParent}, [
  [{...propsTheParentAndChildNeedToKnowAbout}, Child({...propsSpecificToChild})]
]);

To maybe leave using JSX just to where HTML actually needs to be returned? I'm not sure if this is a good idea though, or just confusing.

@trusktr
Copy link
Contributor

trusktr commented Jan 14, 2022

Coming from other systems like React or Custom Elements, this is actually one area that can be more tricky.

I've had success using @lume/element, which wraps solid for templating and reactivity, to achieve these things more easily thanks to ShadowDOM <slot> elements.

I wonder if there's a way to improve this for plain Solid though?

  • should we recommend people use solid-element, and to use actual <slot> elements? (That's pretty easy, but will it not work with SSR?).
  • Can we make new components for solid, f.e. <Slot> and <ShadowRoot>. Even without custom elements, the shadow boundaries would provide composition so that slot attributes would be possible to use.
  • Or maybe we make only a <Slot> component that has nothing to do with ShadowDOM and (somehow?) Solid would know where to place children based on slot attributes from the outer component. I'm not seeing nhow this would work, but the thought is that it would prevent the user from having to learn about the above tricks, and would become more standard.
  • other ideas?

@ryansolid
Copy link
Member

There are patterns around using children helper or using context.

The problem here is it requires an intermediate representation. My usually recommendation is if specialized children return your own like the way <Switch> <Match> do. The problem with these other patterns is they guarantee that extra abstraction. Which is why I'm not going direct people web components for just this reason. I don't think a built Slots method actually help with the render independently bit either.

The basic problem isn't slotting at all. That's easy with JSX. The problem is the desire to have parents augment their children after the fact, which isn't very declarative of a pattern generally. In VDOM libraries you can do this with an abstraction before you do any rendering. We don't have that regardless of how we do the slotting. You still need a way to get the slots. And we do have that already.

@trusktr
Copy link
Contributor

trusktr commented Jan 14, 2022

I'm not sure how "it won't help". The idea is that these intermediate representations are extra cognitive overhead for the common cases that the other abstractions guarantee without the overhead.

When you read someone's code, and you see return [props, <Foo />] it isn't clear what the intent is, plus information (props) is now leaking to some other place.

I think a built in solution would be nice, although I'm not yet sure what it would look like. I'm thinking it might be context based (or something like it).

F.e. maybe we can have a context provider like <Distribute>, and a context receiver like <Slot >, and it could work like this:

function CompA() {
  return <div>
    <p>Distributed content:</p>
    <Slot name="foo" />
  </div>
}
function CompB() {
  return <CompA>
    <Distribute slot="foo">
      <p>This content gets distributed.</p>
    </Distribute>
  </CompA>
}

I don't know if that's the best format, but it provides a standard.

@trusktr
Copy link
Contributor

trusktr commented Jan 14, 2022

The basic problem isn't slotting at all. That's easy with JSX.

How do we slot to a specific location? For example, if we can't inspect that <SomeComp slot="foo"> has a slot attribute, how do we slot it? It seems the above patterns come into play, which I think is what makes this tricky currently.

@trusktr
Copy link
Contributor

trusktr commented Jan 14, 2022

Here's how I would do it with current Solid:

import { render } from "solid-js/web";

function Nav(props) {
  return <div style="border: 2px solid deeppink">
    {props.children ? 
      <div>
        <p>Distributed content: </p>
        {props.children}
      </div>
    : 
      <div>
        <p>Default content.</p>
      </div>
    }
  </div>
}

function App() {
  return <>
    <Nav />
    <Nav>
      <p>This is distributed.</p>
    </Nav>
  </>
}


render(() => <App />, document.getElementById("app"));

playground

If we need more than one "slot" in a component, then I'd use props like this:

import { render } from "solid-js/web";

function Nav(props) {
  return <div style="border: 2px solid deeppink">
    {props.foo ? 
      <div>
        <p>Distributed content (foo): </p>
        {props.foo}
      </div>
    : 
      <div>
        <p>Default content (foo).</p>
      </div>
    }
    {props.bar ? 
      <div>
        <p>Distributed content (bar): </p>
        {props.bar}
      </div>
    : 
      <div>
        <p>Default content (bar).</p>
      </div>
    }
  </div>
}

function App() {
  return <>
    <Nav />
    <Nav
      foo={<p>This is distributed (foo).</p>}
      bar={<p>This is distributed (bar).</p>}
    />
  </>
}


render(() => <App />, document.getElementById("app"));

playground

@trusktr
Copy link
Contributor

trusktr commented Jan 14, 2022

I think that's what you meant by "easy with JSX" right @ryansolid? I think that's the best way to go right now.

I'm not sure if the Distribute/Slot idea would be worth it, but it would provide a standard way to show intent, plus the children-to-be-distributed would be in the actual children area instead of inside attributes/props, which would be more HTML-like. That previous example would be:

  return <>
    <Nav />
    <Nav>
      <Distribute to="foo"><p>This is distributed (foo).</p></Distribute>
      <Distribute to="bar"><p>This is distributed (bar).</p></Distribute>
    </Nav>
  </>

which is closer to what the the original post was going for. Keeping "slotted" children in the actual children area is what is not easy.

@trusktr
Copy link
Contributor

trusktr commented Jan 14, 2022

In case it helps, the structure for tabs in @Brendan-csel's solid-bootstrap (ported from react-bootstrap) is:

<Tabs defaultActiveKey="profile">
  <Tab eventKey="home" title="Home">
    <SomeContent/>
  </Tab>
  <Tab eventKey="profile" title="Profile">
    <SomeContent/>
  </Tab>
  <Tab eventKey="contact" title="Contact">
    <SomeContent/>
  </Tab>
</Tabs>

Interestingly, it is using the return props approach.

@edemaine
Copy link
Contributor

edemaine commented Jan 15, 2022

Would it be possible to allow a Component to return a type T where T extends { jsx: JSX.Element }, and have Solid still build the DOM correctly? Then you could add additional properties to that returned object, which would be great also for returning mutators etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

5 participants