Component Children and Dynamic Children

Previously in the Getting Started tour was an introduction to Class and Style Binding. Components sit in a nested tree and at some point either need to include children that have been passed to them or dynamically add children elements somewhere in the tree.

Component Children

To include the children of a component we use the Children system-defined component. This looks like a component to help remind us that children may be dynamically bound and could change in the component's lifetime.

A simple example of a fancy list wrapper:

import { Children, type ComponentContext, jsx } from 'butterfloat'

interface CoolListProps {}

function CoolList(props: CoolListProps, context: ComponentContext) {
  return (
    <div className="cool-list-wrapper">
      <ul className="cool-list">
        <Children context={context} />
      </ul>
    </div>
  )
}

function ListPage() {
  return (
    <div className="list-page">
      <CoolList>
        <li>This</li>
        <li>Is</li>
        <li>Wrapped</li>
        <li>In</li>
        <li>A</li>
        <li>Cool</li>
        <li>List</li>
      </CoolList>
    </div>
  )
}

To facilitate deeper nesting of component children, the Children component takes the ComponentContext of the component it should bind the children of. (This is the same ComponentContext that provides tools such as events and bindEffect.)

It tries to default to the children of the current component and in this case the context could be omitted because it is embedded entirely inside static HTML, but it is probably a best practice to provide the context in case it does move to somewhere else in the tree with another component in the way.

Dynamic Children Binding

You may need to dynamically change children over time in your components. The lowest level (and currently only way) to do this in Butterfloat is with childrenBind. This binding takes an Observable of Components and as it observes new Components it appends them (by default) as children.

An example dynamic list:

import { jsx } from 'butterfloat'
import { NEVER, concat, delayWhen, from, interval, map } from 'rxjs'

function DynamicList() {
  const children = concat(
    from(['This', 'is', 'a', 'dynamic', 'list']).pipe(
      delayWhen((v, i) => interval(i * 500)),
    ),
    NEVER,
  ).pipe(map((text) => () => <li>{text}</li>))

  return <li childrenBind={children} />
}

This will append new children to the list as they are added.

There is an attribute to control the children binding called childrenBindMode. It defaults to 'append', but can be set to 'prepend' to add new items to the top of the list, or 'replace' to only show the most recent component from the observable.

childrenBind can also be applied to components (hence why components need <Children /> to display their children) and fragments as well (by expanding <></> to <Fragment childrenBind={children}></Fragment>).

Comments and the Empty Component

The <Comment comment="Some static comment" /> pseudo-component is useful for adding HTML comments. The web was built on View Source, and sometimes it is still nice to add visible comments to the DOM.

The <Empty /> pseudo-component is an explicitly empty return state. It's a bit more optimized than alternatives like using an empty fragment (<></>, the "empty fish").

Next Steps

If you have gotten this far in the general tour you might be interested in Suspense and Advanced Binding.