Context

Warning

Currently Disabled The code for context is still there but unused. I plan to re-enable when switching away from threadlocal to maybe asyncio ContextVar.

When you really embrace components and subcomponents, you end up with deeply nested component trees. It can be frustrating, not to mention brittle, to pass data all the way from the top to the bottom. Intermediate components don’t use the data. Why should they have to depend on them?

Pass-Through

This is the harder, manual way.

In this example the site object is passed through the App component, then Nav, to get it to NavHeading. Neither App nor Nav need anything from site. They are just transits.

def NavHeading(title):
    """A navigation heading component."""
    return html("<h1>{title}</h1>")


def Nav(site):
    """A navigation component."""
    return html('<nav><{NavHeading} title={site["title"]}/></nav>')


def PageHeading(title):
    """Title block for a page."""
    return html("<h2>{title}</h2>")


def Main(page):
    """Full page layout."""
    return html('<{PageHeading} title={page["title"]}/>')


def App(site, page):
    """An app for a site."""
    return html(
        """
        <{Nav} site={site}/>
        <{Main} page={page}/>
    """
    )


def main():
    """Main entry point."""
    site = dict(title="My Site")
    page = dict(title="My Page")

    return render(
        html(
            """
        <{App} site={site} page={page} />
"""
        )
    )

Context

This is the easier way.

React has addressed this with a number of technologies over the years, including Context and Hooks. viewdom has a similar “context” construct, where you can wrap a part of the component tree, shove values into the rendering, and pluck them out later on. We can use this to make site available anywhere in the tree:

def NavHeading():
    """A navigation heading component."""
    site = use_context("site")
    title = site["title"]
    return html("<h1>{title}</h1>")


def Nav():
    """A navigation component."""
    return html("<nav><{NavHeading}/></nav>")


def PageHeading(title):
    """Title block for a page."""
    return html("<h2>{title}</h2>")


def Main(page):
    """Full page layout."""
    return html('<{PageHeading} title={page["title"]}/>')


def App(page):
    """An app for a site."""
    return html(
        """
        <{Nav}/>
        <{Main} page={page}/>
    """
    )


def main():
    """Main entry point."""
    site = dict(title="My Site")
    page = dict(title="My Page")

    return render(
        html(
            """
        <{Context} site={site}>
            <{App} page={page} />
        <//>
"""
        )
    )

As you can imagine, a React Hooks approach could also be implemented.