Component with children

Describes how to create components with children

A component class derives from Component and must implement the Render() method. Inside it, local fields, properties, and of course State properties of stateful components are directly accessible and can be used to compose the resulting view.

Components can also render their children by calling the base method Children(). This opens up a powerful feature that can be useful if we want to build a component that arranges its children in some way.

Say we want, for example, to create a component that arranges its children within a customizable grid, like this:

To start, let's create a component that builds our page:

class PageComponent : Component
{
    public override VisualNode Render()
        => NavigationPage(
            ContentPage("Component With Children")
            );
}

This should show an empty page with just a title, then create a component for our grid (call it WrapGrid)

public class WrapGrid : Component
{
    public override VisualNode Render()
    {
    }
}

Every Component class can access its children using the Children() method (it's similar to the {this.props.children} property in ReactJS)

class WrapGrid : Component
{
    public override VisualNode Render()
        => Grid(Children());
}

We can add a ColumnCount property and simple logic to arrange and wrap any children passed to the component like this:

partial class WrapGrid : Component
{
    [Prop]
    private int _columnCount = 4;

    public override VisualNode Render()
    {
        int rowIndex = 0, colIndex = 0;

        int rowCount = Math.DivRem(Children().Count, _columnCount, out var divRes);
        if (divRes > 0)
            rowCount++;

        return new Grid(
            Enumerable.Range(1, rowCount).Select(_ => new RowDefinition() { Height = GridLength.Auto }),
            Enumerable.Range(1, _columnCount).Select(_ => new ColumnDefinition()))
        {
            Children().Select(child =>
            {
                child.GridRow(rowIndex);
                child.GridColumn(colIndex);
                
                colIndex++;
                if (colIndex == _columnCount)
                {
                    colIndex = 0;
                    rowIndex++;
                }

                return child;
            }).ToArray()
        };
    }
}

Finally, we just need to create the component from the main page and fill it with a list of child buttons:

class PageState
{
    public int ColumnCount { get; set; } = 1;

    public int ItemCount { get; set; } = 3;
}

class PageComponent : Component<PageState>
{
    public override VisualNode Render()
    {
        return new NavigationPage()
        {
            new ContentPage()
            {
                new StackLayout()
                { 
                    new Label($"Columns {State.ColumnCount}")
                        .FontSize(14),
                    new Stepper()
                        .Minimum(1)
                        .Maximum(10)
                        .Increment(1)
                        .Value(State.ColumnCount)
                        .OnValueChanged(_=> SetState(s => s.ColumnCount = (int)_.NewValue)),
                    new Label($"Items {State.ItemCount}")
                        .FontSize(Xamarin.Forms.NamedSize.Large),
                    new Stepper()
                        .Minimum(1)
                        .Maximum(20)
                        .Increment(1)
                        .Value(State.ItemCount)
                        .OnValueChanged(_=> SetState(s => s.ItemCount = (int)_.NewValue)),

                    new WrapGrid()
                    { 
                        Enumerable.Range(1, State.ItemCount)
                            .Select(_=> new Button($"Item {_}"))
                            .ToArray()
                    }
                    .ColumnCount(State.ColumnCount)                            
                }
                .Padding(10)
                .WithVerticalOrientation()
            }
            .Title("Component With Children")
        };
    }
}

Last updated