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

Add fragment API to allow returning multiple components from render #2127

Closed
AdamKyle opened this issue Sep 2, 2014 · 148 comments
Closed

Add fragment API to allow returning multiple components from render #2127

AdamKyle opened this issue Sep 2, 2014 · 148 comments

Comments

@AdamKyle
Copy link

AdamKyle commented Sep 2, 2014


Note from maintainers:

We know this is an issue and we know exactly what set of problem can be solved. We want this too but it is a hard problem with our current architecture. Additional comments expressing desire for this feature are not helpful. Feel free to subscribe to the issue (there's button in the right hand column) but do not comment unless you are adding value to the discussion. "Me too" and "+1" are not valuable, nor are use cases that have already been written in the comments (e.g., we know that you can't put <tr> or <dd> elements with a <div>).


Consider the following:

var ManagePost = React.createClass({

  render: function() {
    var posts = this.props.posts

    var something;
    var somethingelse;

    var row = posts.map(function(post){
      return(
        <div>
          <div className="col-md-8">
          </div>
          <div className="cold-md-4">
          </div>
        </div>
      )
    });

    return (
        {row}
    );
  }

});

If you remove the <div></div> in the map, you get the following error: Adjacent XJS elements must be wrapped in an enclosing tag

it isn't till I re-add the surrounding, and rather pointless, divs that it compiles with out issue. I am running 0.11.1

Is this being addressed? It adds extra, and again - IMO - useless and pointless html to the page, that while harming nothing - looks messy and unprofessional. Maybe I am just doing something wrong, please enlighten me if I am.

@chenglou
Copy link
Contributor

chenglou commented Sep 2, 2014

Because when you don't put the wrapper, it desugars to this:

return React.DOM.div(...)React.DOM.div(...)

Which doesn't make syntactical sense. The jsx compiler page might help if you need a visual mapping.

That being said, it's possible to desugar this to [div, div] instead. This is hard, somewhat controversial and won't be implemented in a near future.

@sophiebits
Copy link
Collaborator

(I don't think it's particularly controversial but it does add code complexity and hasn't been done yet.)

@chenglou
Copy link
Contributor

chenglou commented Sep 2, 2014

IIRC @syranide has a few comments on this

@syranide
Copy link
Contributor

syranide commented Sep 2, 2014

@chenglou Hehe.

I had a brief chat about it with chenglou a little while ago, I don't really lean one way or the other at the moment. I see a lot of hidden dangers in allowing a composite components to return multiple components and it breaks a lot of intuitive assumptions, but I don't know of any (right now) good practice use-cases that would obviously benefit from this.

The simplicity of returning at most one component means that it's very easy to reason about what you see, if not, <table><TableHeader /></table> could actually render any number of rows, you have no way of knowing other than inspecting TableHeader and whatever composite components it returns.

I feel like being able to return multiple components only moves the responsibility of wrapping components as necessary from the "composite component" to whatever "renders the composite component". The component "that renders composite component" rarely has or should have the knowledge of whether to wrap composite components or not, whereas children are more likely to have knowledge of their parents.

But perhaps it's simply a case of developer responsibility. There may be good use-cases for both and we should just look past the inevitable misuse.

@sebmarkbage Probably has a few comments too :)

@sebmarkbage
Copy link
Collaborator

We will probably never allow that syntax implicitly. You would need a wrapper like

<>
  <div className="col-md-8">
  </div>
  <div className="cold-md-4">
  </div>
</>

OR

[
  <div className="col-md-8">
  </div>,
  <div className="cold-md-4">
  </div>
]

However, even this doesn't work. Often, it's probably for the best. It can be confusing for consuming components when a child might expand into multiple elements.

But, really, the only reason we don't support this right now is because it's hard to implement. Hopefully, we'll be able to support it sometime in the future but probably not short term. Sorry. :/

@AdamKyle
Copy link
Author

AdamKyle commented Sep 3, 2014

can this not affect things like jquery or other libraries that target specific elements, so if you do something like $('#id-name').children(), the following:

<div id="id-name">
  <div>
    <div class="class-name">
    </div>
  </div>
</div>

the <div> and <div class="class-name"> would be selected in this case. (If I understand this properly)

@DAddYE
Copy link

DAddYE commented Oct 13, 2014

It affects also css selectors in the very same way @AdamKyle posted before.

@srph
Copy link
Contributor

srph commented Nov 16, 2014

Any updates on this issue?

@gabssnake
Copy link

Spent a couple of minutes understanding why my component didn’t work. I feel like there should be a notice somewhere, maybe I missed it ? Maybe it is obviously wrong to try :

var Optimistic = React.createClass({
  render: function() {
    return ( 
      <h1>{this.props.name} loves React</h1>
      <p>React doesn’t. Idea: sprinkle some divs here and there.</p>
    );
  }
});

React.render(
  <Optimistic name="Peter" />,
  document.getElementById('myContainer')
);

@sophiebits
Copy link
Collaborator

@gabssnake You should have gotten a JSX compile error with the error "Adjacent XJS elements must be wrapped in an enclosing tag"; did you not see the error or was it not clear in its explanation?

@gabssnake
Copy link

Thank you for your answer @spicyj. Well, I meant a notice in the React documentation. Yes, the console did show an error, but the need to wrap didn’t make sense to me at first. That is why I searched and got here.

@GetContented
Copy link

I've had this pain as well... particularly painful for my designer, as a matter of fact. It'd be kind of nice if a component could output a node (therefore node-list or fragment) instead of an element.

@gaearon
Copy link
Collaborator

gaearon commented Jan 28, 2015

Just saying.. I'm not advocating returning multiple children from component but I'd love to do that in render* methods that I extract from render:

  render: function () {
    return (
      <div className={this.getClassName()}
           style={{
             color: this.props.color,
             backgroundColor: this.props.backgroundColor
           }}>
        {condition ?
          this.renderSomething() :
          this.renderOtherThing()
        }
      </div>
    );
  },

  renderSomething() {
    return (
      <>
        <div className='AboutSection-header'>
          <h1>{this.props.title}</h1>
          {this.props.subtitle &&
            <h4>{this.props.subtitle}</h4>
          }
        </div>,

        {hasChildren &&
          <div className='AboutSection-extra'>
            {this.props.children}
          </div>
        }
      </>
    );
  }

But I should probably just shut up and use keys.

@syranide
Copy link
Contributor

@gaearon You can do that already though, you just need to return an array for now (which is a bit cumbersome though yes) ... buuuuut, you can work around that, I have hacked my own <Frag> component which is translated to an array (overloaded React.render) ... you could also do return <NoopComp>...</NoopComp>.props.children I assume, if you want to avoid hacks.

EDIT: My bad, I overloaded React.createElement not React.render.

@gaearon
Copy link
Collaborator

gaearon commented Jan 28, 2015

The problem with arrays is they trip our designer. Need commas, explicit keys.

@syranide
Copy link
Contributor

@gaearon Yeah you can avoid the commas using either of my two workarounds for now (if you find either acceptable)... but what you do mean with explicit keys?

@gaearon
Copy link
Collaborator

gaearon commented Jan 29, 2015

If I use array syntax, I need to specify key on each element. Not that it's hard to do, but feels awkward because I know they never change.

@syranide
Copy link
Contributor

@gaearon Ah yes, I choose to just mentally ignore that warning for now :), if you really want to to avoid it, you can do <MyComp children={this.renderWhatever()} /> to avoid it (EDIT: although you obviously can't use that if you have adjacent children, you could use some flattening helper... but yeah).

@natew
Copy link

natew commented Feb 11, 2015

Another case I've ran into with a UI kit. When you are placing children inside a fixed scrollable container like so:

return (
  <div style={{
    position: fixed
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    overflow-y: scroll;
  }}>
    {this.props.children}
  </div>
);

The children now must be passed as an array, but if a composite class is passed, it must also implement those styles to avoid breaking it. Just adds a layer of complexity. But I can completely understand how complex it must be to make the change. Just throwing my hat in for support.

@sophiebits sophiebits changed the title Why is it the divs them selves must be wrapped in divs? Add fragment API to allow returning multiple components from render Feb 27, 2015
@Prinzhorn
Copy link

I have another use-case I described in detail over here #3415. But I can workaround it for now and understand that it is hard to implement and rare.

I know nothing about react internals (yet), but I'll just throw in an idea how you could mark such virtual parent elements/fragments in the DOM: comments. E.g.

<A>
    <B></B>
    <Fragment>
        <C></C>
        <D></D>
    </Fragment>
    <E></E>
</A>

would render to

<a>
    <b></b>
    <!--<fragment data-reactid="">-->
        <c></c>
        <d></d>
    <!--</fragment>-->
    <e></e>
</a>

This means c and d will be treated as children of sth. (including the nested reactid to not conflict with b and e). I've seen other frameworks "misuse" comments for these kind of semantical jobs.

@GetContented
Copy link

@Prinzhorn I think you might be confusing DOM output with React's JSX.

@aldendaniels
Copy link

In case it's useful: @Prinzhorn's suggestion of using HTML comments to map "fragment components" to the DOM is the same approach that Knockout uses. Knockout calls these "virtual elements".

<!-- ko component: "message-editor" -->
<!-- /ko -->

(from knockout docs)

@aldendaniels
Copy link

Also, another use case for this - the extra wrapping elements can be problematic when using flexbox.

@kilianc
Copy link

kilianc commented Jul 26, 2017

🦏

@zheeeng
Copy link

zheeeng commented Jul 27, 2017

@gaearon Awesome improvement! Is it support pure text node?

@gaearon
Copy link
Collaborator

gaearon commented Jul 27, 2017

Yes, it supports returning strings too.

@diligiant
Copy link

May I ask how is this supposed to work?

I was hoping it would allow us to render 2 adjacent XJS elements but if I do return ["foo", "bar"] (something more useful ;), I get the expected bundle.js:66656 Warning: Each child in an array or iterator should have a unique "key" prop.

So was the feature a way not to surround a real list by an extraneous element?

@JesperTreetop
Copy link

JesperTreetop commented Aug 15, 2017

So was the feature a way not to surround a real list by an extraneous element?

Yes, a way to provide several elements from one render. (For more information, see the initial issue post.)

If all your array items are known to be strings, just join them and return a single string. If not, you can either eat this warning or work out what to do to wrap them in elements so that they can be keyed to improve DOM reconciliation, just as you would have if you had included an array of strings inside another element in JSX.

@sassanh
Copy link
Contributor

sassanh commented Aug 15, 2017

@diligiant returning ['foo', 'bar'] is not relevant to this issue. You never could and still can't do return <div>{['foo','bar']}</div> Each child in an array should have a 'key' prop whether it's in an internal jsx tag like <div> or it's being returned. What you can do now is:

return [<div key='1'>foo</div>, <span key='2'>bar</span>];

It removes a big limitation in react.

Btw returning ['foo', 'bar'] gives you a warning not an error and to remove that warning you can easily join those strings or if they're jsx tags and not strings you can add a key prop to them. So there's no limitation about returning arrays.

@diligiant
Copy link

diligiant commented Aug 15, 2017

@JesperTreetop thank you.
@sassanh, from your example, it seems that I've been too "shy" to use keys "just" to avoid a surrounding (semantically) useless <div>. Do you mean I should go ahead and that there is no real performance penalty? That would indeed be a REAL improvement!

@sassanh
Copy link
Contributor

sassanh commented Aug 15, 2017

@diligiant I doubt it introduces any performance penalty but you don't need a surrounding useless div unless it's surrounding strings and if it's surrounding strings you can avoid array at all and join strings. If it's not strings you can add the key to the component. For example if you have two components Foo and Bar you can return [<Foo key='foo'/>, <Bar key='bar'/>] and you don't need to surround neither your components (like always) nor your array (thanks to this new release, you needed to surround the array before 16).

@diligiant
Copy link

diligiant commented Aug 15, 2017

@sassanh cool then. Of course my use case was with multiple components. Happy!! ;)

@gaearon
Copy link
Collaborator

gaearon commented Aug 15, 2017

Actually it seems odd to me that we warn about missing keys when all items are strings. I wouldn't expect us to do so. Is this existing behavior in 15 when strings are inside a div? If not then we should fix it in 16 for top-level strings (since it's impossible to give keys to them).

@diligiant
Copy link

@gaearon sorry, my example was misleading: I meant components.

I checked and under -beta.5, React doesn't issue a warning when rendering an array with multiple strings.

Nonetheless, I am puzzled by the key requirement when rendering an array just for the sake of avoiding a surrounding component. I understand and it's nothing but it will probably raise tons of questions on SO (I have nothing better to offer though…)

And finally, thank you again.

@JesperTreetop
Copy link

JesperTreetop commented Aug 15, 2017

This warning is the exact same as for every other usage of arrays of components, because it boils down to the exact same "extra work" for the reconciler, and thus the exact same potential optimization by setting a key. To me the surprise would be if arrays of components were to be treated differently depending on this, and I imagine that would prompt some Stack Overflow questions too. Not to mention that I think it would involve some overhead just to keep track of it in the first place.

@zwily
Copy link

zwily commented Aug 15, 2017

.. and most of the time I will be returning an array from a function, I do want the key warning in place. It seems the times you wouldn't want the warning are the minority, and there's no way for React to know if it's appropriate to not warn.

@gaearon
Copy link
Collaborator

gaearon commented Aug 15, 2017

The warning is necessary because lack of keys can cause correctness issues, not just performance problems. This is explained in many other issues asking why keys are necessary so I encourage you to search for them and read those discussions. I agree the docs could be clearer about this, and it's something we'll likely look at next time we do a docs change sprint.

There is no conceptual differences between arrays directly returned from render, and arrays inside a div. So there is no reason why there would be a key warning in one case but not the other. They need to work the same way because both are affected by the same problems when the keys are missing.

That said we understand it's annoying to specify keys for static content. Just like you don't specify keys when you write a JSX structure where children are statically known (and thus never reorder), it would be nice to have a way to do this with arrays.

In the future we might solve this by adding explicit support for fragments in JSX with syntax like:

return (
  <>
    <div>child 1</div>
    <div>child 2</div>
  </>
);

It could produce an array but implicitly assign numeric indexes to the children because in this case we know they can never reorder. This guarantee given by JSX child expression is exactly what lets us get away without specifying keys in normal JSX code where a div might have multiple children.

But we don't have this syntax yet. So for now this is a known limitation.

@diligiant
Copy link

@JesperTreetop & @zwily, @gaearon explained it way better than I did ;)

Once you know, it's no big deal but as we all want React to thrive, I was just saying…

@smrq
Copy link

smrq commented Aug 15, 2017

@gaearon Is there another issue for the <> syntax proposal that we can watch instead of further discussion on this issue? I searched around but I wasn't able to find one.

@veged
Copy link

veged commented Aug 15, 2017

@smrq +1 to question — I follow everything about those awkward limitations (one-to-one rendrer() result, keys and JSX syntax or fragments) but the only ticket I know is facebook/jsx#65

I also suppose that fibers gonna fix issue with keys at all — but looks like it's unfulfilled dream

@sophiebits
Copy link
Collaborator

Keys aren't an "issue". :) They're a fundamental and necessary part of any view framework that allows you to create dynamic lists.

See https://facebook.github.io/react/tutorial/tutorial.html#keys for a more detailed explanation.

@veged
Copy link

veged commented Aug 15, 2017

@spicyj actually it is an «issue» since it cause extended energy spendings from developers and there is fundamental possibility to program view framework without such requirement (for example https://github.com/dfilatov/vidom)

@goto-bus-stop
Copy link

there is fundamental possibility to program view framework without such requirement (for example https://github.com/dfilatov/vidom)

vidom does use keys in collections though. It might technically work without it but it would likely be much slower. React could also technically work without keys, but it'd be quite unexpected to find that half of your components have to update when you remove a single item from a list. With keys, only one item is unmounted.

@veged
Copy link

veged commented Aug 15, 2017

@goto-bus-stop vidom can use keys, but they are not required and without them only really big cases with a lot of updates can cause real performance problems

so I consider this part as possible optional (as, for example, shouldComponentUpdate) which can be used for ferformance tweaks in individual cases

@brigand
Copy link
Contributor

brigand commented Aug 15, 2017

@veged example of vidom struggling without keys.

It has no idea it's supposed to reorder the elements, so it discards the instances on each render of the root component.

@leeoniya
Copy link

leeoniya commented Aug 15, 2017

as someone who's quite familiar with the virtual-dom space [1]. i can say that:

  1. keys are necessary for predictable dom & state updates amongst similar siblings.
  2. keys are typically not an optimization, and - in fact - are usually the opposite.

[1] https://github.com/leeoniya/domvm

@veged
Copy link

veged commented Aug 15, 2017

however, the unambiguous issue here (as @gaearon described) is a fully static usage of arrays and the difference between static arrays and static JSX fragments

@veged
Copy link

veged commented Aug 15, 2017

@brigand O have no doubt that reordering of state-full components may cause some issues ;-) but force every other cases (in my opinion more of them) to struggle for this... looks controversial

@gaearon
Copy link
Collaborator

gaearon commented Aug 15, 2017

Keys are important to React. Even if they don't seem to matter in some case (e.g. because components below are all stateless) there is nothing preventing you or someone else on the team to add a stateful component (or even a plain DOM input) somewhere a few levels below in a few months. By then you might forget that you don't have keys, and thus reorders may cause the state (or input value) to be associated with a wrong item.

This is why we encourage you to specify keys everywhere for dynamic lists. Not doing so leads to very hard-to-trace bugs, and we think it's better to spend extra ten seconds specifying the key than ten hours debugging why the state of a component several levels below gets messed up in some corner case.

I fully agree it's inconvenient to specify keys when the list is known to be static and never reorders. You are welcome to discuss this on the JSX repo. If you can't find an issue for this, you are welcome to create a new one there.

Since this thread has a lot of subscribers and the feature has been implemented I would like to lock it to avoid spamming a lot of people with notifications. I hope the clarifications above address your concerns, but if not, you are welcome to create a new issue for further discussion on a specific topic.

@facebook facebook locked and limited conversation to collaborators Aug 15, 2017
@sophiebits
Copy link
Collaborator

@smrq created a proposal issue for <> syntax on the jsx repo: facebook/jsx#84.

@gaearon
Copy link
Collaborator

gaearon commented Nov 28, 2017

We just released support for a new React.Fragment export and the associated <> syntax:
https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests