Get the Newsletter

How do we React? - Part 3

Posted by Rob Eisenberg on March 13, 2019

This is part 3 of 3 in a series titled "How do we React?" in which I conclude our discussion of how Aurelia handles common React scenarios.

The other week Sebastian Markbåge posted the following tweet :

You may have noticed that most other frameworks don't have HoCs, render props or anything like React.Children. These account for a lot the differences between React and other frameworks. How would you solve these use cases if you had to switch to [other framework]?

In this post, we'll cover React.Children For a discussion of how Aurelia handles typical HoC scenarios, please see part 1 or for information on how we handle render props, see part 2.

React.Children

From Sebastian's original tweet, it wasn't clear to me whether he was referring to the React.Children helper or to a React component's props.children . The utility seems less essential to me, so I'll focus primarily on props.children here, but I'll also make a few notes relating to React.Children.

The React site states :

Some components don’t know their children ahead of time. This is especially common for components like Sidebar or Dialog that represent generic "boxes". We recommend that such components use the special children prop to pass children elements directly into their output.

Slots

If you've read part 2 of this series, then you may have already realized that this is handled quite simply by Aurelia's adoption of the Shadow DOM web standard. As an example, the FancyBorder component shown on the React site can be authored in Aurelia like this:

    
  <template bindable="color" class="fancy-border-${color}">
    <slot></slot>
  </template>
  
  

In fact, we don't even need to write any JavaScript at all to implement this. Because these types of container components are so common, Aurelia provides a mechanism for creating HTML-only components, which is what I've shown above. Here's how it's used:

    
  <fancy-border color="red">
    Hello from Aurelia!
  </fancy-border>
  
  

As a reminder, or if you aren't familiar with Shadow DOM, the HTML child nodes of the fancy-border element will automatically be projected into the location of the slot element. That's all there is to it.

Because we've adopted the Shadow DOM slot composition model, we can also declaratively use named slots, slot fallback content, and re-project children from a slot in one element to a slot in a nested component. All that comes for free.

Web Standards

A core value of Aurelia has always been to be as standards compliant as possible. Besides the natural benefit of working with browsers rather than against them, anyone who knows the standards already knows most of Aurelia. And those who are less familiar with the standards will learn many of them while using Aurelia, which is an excellent career side-effect for any front-end engineer.

The @children Decorator

For the most part, something like React.Children isn't needed in Aurelia, since developers are typically working with slot-based composition or simple collections of plain JavaScript objects.

That said, you may wonder how a container component would gain access to its child components directly, so that it can manipulate them, query them, or build other UI based on them. If you have that scenario in Aurelia, all you need to do is decorate a property with the @children decorator. You can optionally specify a CSS selector, to filter which children you want to work with. For example, if you were building a tab control, you could do something like this:

    
  export class TabControl {
    @children('tab-item') items;
  
    selectTab(item) {
      // ellided
    }
  }
  
  

With this in place, you have a live list of all the component view models for tab-item components that are children of a tab-control. So, you could build a view for the tab-control like this:

    
  <template>
    <ul class="tab-strip">
      <li repeat.for="item of items">
        <a click.trigger="selectTab(item)">${item.heading}</a>
      </li>
    </ul>
  
    <div class="tab-items">
      <slot></slot>
    <div>
  </template>
  
  

With the view above, the tab-item components will be rendered at the location of the slot. However, in addition to that, an li will be rendered for every tab-item child, with the text content of the a set to the heading property of the item.

In other words, you could write this...

    
  <template>
    <tab-control>
      <tab-item heading="Tab 1">
        Content of tab 1.
      </tab-item>
      <tab-item heading="Tab 2">
        Content of tab 2.
      </tab-item>
      <tab-item heading="Tab 3">
        Content of tab 3.
      </tab-item>
    </tab-control>
  </template>
  
  

And it would render something like this:

    
  <tab-control>
    <ul class="tab-strip">
      <li>
        <a>Tab 1</a>
      </li>
      <li>
        <a>Tab 2</a>
      </li>
      <li>
        <a>Tab 3</a>
      </li>
    </ul>
  
    <div class="tab-items">
      <tab-item heading="Tab 1">
        Content of tab 1.
      </tab-item>
      <tab-item heading="Tab 2">
        Content of tab 2.
      </tab-item>
      <tab-item heading="Tab 3">
        Content of tab 3.
      </tab-item>
    <div>
  </tab-control>
  
  

Because Aurelia's templating and binding systems are deeply integrated, you could drive the entire tab control with data also, like this:

    
  <template>
    <tab-control>
      <tab-item repeat.for="n of 3" heading="Tab ${n}">
        Content of tab ${n}.
      </tab-item>
    </tab-control>
  </template>
  
  

This would result in the same final HTML composition as the static version above.

Wrapping Up

This post was quite a bit shorter than part 2, primarily because much of what we covered in part 2 also addresses the scenarios for React.Children and props.children. Aurelia's standards-based slot composition model, combined with powerful decorators like @children enable a very declarative model for what would otherwise be complex UI patterns.

We hope you've enjoyed this three part series and now have a better idea of how Aurelia provides its own approaches to various React scenarios. If you're curious to see what Aurelia vNext has in store, or to learn more about Aurelia and how it compares to other frameworks, please subscribe to this blog. There's much more goodness to come.

Cheers!