rmorse / babel-plugin-jsx-template-vars

A Babel transform for rendering a template version of your React / Preact app. Useful for generating a pre-render for SSR.
MIT License
10 stars 0 forks source link

Nested components and children #10

Open luisherranz opened 2 years ago

luisherranz commented 2 years ago

Very interesting stuff 🙂

Do you have any plans for supporting nested components and children?

const Button = ({ children }) => <button>{children}</button>;
Button.templateVars = ["children"];

const App = ({ message }) => <Button>{message}</Button>;
App.templateVars = ["message"];
rmorse commented 2 years ago

Hey @luisherranz

I think this would already work technically (it should work in the demo project)... But you wouldn't need both at the same time (ie only on the App level or Button level).

But I'm not sure this is what you mean exactly.. I think what you're getting at is kindof a hierarchy... like how do we understand the relationship between app + button? Right now in a "template", everything comes out "flat" so if you have nested components, we'll still need to supply data like (of course we don't need both):

$data = array(
    'message' => 'My message',
    'children' => 'Duplicate entrypoint to the same data',
);

The only thing I don't like (in this plugins current state) about using children, is that is what is exposed to the PHP / Handlebars template. So the data supplied server side would need to be called children and of course we could get duplicates in different components.

I haven't yet started thinking about this too much but I know there is a bigger problem of nesting / hierarchies that needs addressing - how do we signify relationships between components (is totally it necessary? I don't know yet but it needs investigating more).

Another approach to solve duplicate children issue is to allow a property in configuration to name the variable (which should be pretty easy to implement):

const Button = ({ children }) => <button>{children}</button>;
Button.templateVars = [ [ "children", { name: 'button_text' } ] ];

const App = ({ message }) => <Button>{message}</Button>;

In this case I opted to remove "message" from the App to illustrate.

I think this stuff becomes more important again when thinking about nested components in loops for example, as I've just highlighted in this ticket - which is something I'm working on now to solve a real issue I'm facing.

Thanks for the feedback and let me know if I understood/missunderstood 👍

luisherranz commented 2 years ago

everything comes out "flat" so if you have nested components, we'll still need to supply data like

Ohh, ok. I get it now, thanks.

So the data supplied server side would need to be called children and of course we could get duplicates in different components.

Why do you need children if you're flattening it out? Shouldn't you replace children with the resulting markup generated by the parent?

Imagine:

const Message = ({ className, children }) => (
  <>
    <strong>Message: </strong>
    <span className={className}>{children}</span>
  </>
);

const App = ({ title, message }) => (
  <>
    <h1>{title}</h1>
    <div>
      <Message className="my-message">This is my {message}</Message>
    </div>
  </>
);
App.templateVars = ["title", "message"];
<h1>
  <?php echo $data["title"]; ?>
</h1>
<div>
  <strong>Message: </strong>
  <span class="my-message">
    This is my <?php echo $data["message"] ?>
  </span>
</div>

I think this stuff becomes more important again when thinking about nested components in loops for example

Yeah. At first glance, it seems like flattening everything out is simpler. But I wonder if the complexity will grow over time without reproducing the concept of components in PHP.

Do you have any thoughts about that?

rmorse commented 2 years ago

Why do you need children if you're flattening it out? Shouldn't you replace children with the resulting markup generated by the parent?

Yeah exactly, we should probably just set the message on the App level, and we can forget about children, but I imagine there are some use cases where you might want to set it on the message level for example.

Your code example is correct, it's how it should work today in it's current state.

Yeah. At first glance, it seems like flattening everything out is simpler. But I wonder if the complexity will grow over time without reproducing the concept of components in PHP.

This is an interesting idea... it would be cool to break it down into to components (and challenging) but still need to figure out how necessary it is...

Right now I can think of 3 levels to this:

  1. Keep it flat (as we already discussed) and maybe use naming of exposed variables to avoid collisions
  2. We could kindof "namespace" PHP keys: $data['app/message/children']
  3. Or we could completely go another route and deconstruct the whole thing into component parts - still not sure what that would look like yet...

I do like the idea of number 3 in principle but I think it would need more testing to see if its even viable with this approach.

Right now the way this works is it allows a regular render of an app, and pulls out the rendered html.

And we inject bits of "code" (just html strings) and occasionally change the logic (so certain things are always rendered, even if the a condition says it should not be).

To break it into seperate components - I'm not sure what that would look like yet - or if its possible - we would need to greatly alter the output application and render clean components side by side...

Some thoughts / questions:

  1. If we did have multiple components in PHP and reconstruct the app essentially, how could we supply the data, considering some components are responsible for their own data (not passed in as props) - eg useinstanceId()
  2. What does a complicated use case look like so we could try to test some of these theories, to see how "ugly" it gets...

These are not questions for you specifically but good questions I think that are worth investigating... I would like to see how far we could get this.


Just to add, the nesting of components in loops, I think will be viable in the current paradigm with some work. This is my current thinking and what I'm working to towards at the moment (please excuse the not completely realistic example 🙄):

const Radio = ({ className, value, children }) => {
    const [ checked, setChecked ] = useState( false );
    const uid = useInstanceId( Radio );
    return (
        <>
            <input type="radio" id={ uid } className={className} value={value} checked={checked} />
            <label for={ uid }>{children}</label>
        </>
    );
};
Radio.templateVars = ["checked", "value", "uid", "children"];

const App = ({ title }) => {
    const radios = [ 
        { value: 'radio1', label: 'Radio 1' },
        { value: 'radio2', label: 'Radio 2' },
    ];

    return (
        <>
            <h1>{title}</h1>
            <div>
                { radios.map( ( radio ) => (
                    <Radio className="my-radio" value={radio.value}>{radio.label}</Radio>
                ) ) }
            </div>
        </>
    );
};
App.templateVars = ["title", ["radios", {type: "list"} ]];

Here it probably make more sense to have some of templateVars on the Radio itself, and then we could supply data like:

$data['title'] = 'My app title';
$data['radios'] = array(
    array(
        'children' => 'Radio 1',
        'value' => 'radio1',
        'uid' => 'radio1',
        'checked' => true,
    ),
    array(
        'children' => 'Radio 2',
        'value' => 'radio2',
        'uid' => 'radio2',
        'checked' => false,
    ),
);

In this instance it would be cool to be able to rename "children" (although this is a contrived example, and we would probably just pass in "label" instead)..

luisherranz commented 2 years ago

If we did have multiple components in PHP and reconstruct the app essentially, how could we supply the data, considering some components are responsible for their own data (not passed in as props) - eg useinstanceId()

I was thinking about a "server scope function" per component. Code should be co-located.

// my-site-title/save.js
const MySiteTitle = ({ siteTitle, Tag }) => (
  <>
    <Tag>{siteTitle}</Tag>
  </>
);
App.templateVars = ["siteTitle"];
// my-site-title/save.php
funciton save( $attributes ) {
  $siteTitle = get_bloginfo( 'name' );

  return [
    'siteTitle' => $siteTitle
  ];
}

Just to add, the nesting of components in loops, I think will be viable in the current paradigm with some work.

Looks nice 🙂

rmorse commented 2 years ago

Oooh interesting idea! Been busy on some other projects but hoping to get back into this fully in about a week.