Your First Components#

As we learned earlier we can use ReactPy to make rich structured documents out of standard HTML elements. As these documents become larger and more complex though, working with these tiny UI elements can become difficult. When this happens, ReactPy allows you to group these elements together info “components”. These components can then be reused throughout your application.

Defining a Component#

At their core, components are just normal Python functions that return HTML. To define a component you just need to add a @component decorator to a function. Functions decorator in this way are known as render function and, by convention, we name them like classes - with CamelCase. So consider what we would do if we wanted to write, and then display a Photo component:

from reactpy import component, html, run


@component
def Photo():
    return html.img(
        {
            "src": "https://picsum.photos/id/237/500/300",
            "style": {"width": "50%"},
            "alt": "Puppy",
        }
    )


run(Photo)

Warning

If we had not decorated our Photo’s render function with the @component decorator, the server would start, but as soon as we tried to view the page it would be blank. The servers logs would then indicate:

TypeError: Expected a ComponentType, not dict.

Using a Component#

Having defined our Photo component we can now nest it inside of other components. We can define a “parent” Gallery component that returns one or more Profile components. This is part of what makes components so powerful - you can define a component once and use it wherever and however you need to:

from reactpy import component, html, run


@component
def Photo():
    return html.img(
        {
            "src": "https://picsum.photos/id/274/500/300",
            "style": {"width": "30%"},
            "alt": "Ray Charles",
        }
    )


@component
def Gallery():
    return html.section(
        html.h1("Famous Musicians"),
        Photo(),
        Photo(),
        Photo(),
    )


run(Gallery)

Return a Single Root Element#

Components must return a “single root element”. That one root element may have children, but you cannot for example, return a list of element from a component and expect it to be rendered correctly. If you want to return multiple elements you must wrap them in something like a html.div:

from reactpy import component, html, run


@component
def MyTodoList():
    return html.div(
        html.h1("My Todo List"),
        html.img({"src": "https://picsum.photos/id/0/500/300"}),
        html.ul(html.li("The first thing I need to do is...")),
    )


run(MyTodoList)

If don’t want to add an extra div you can use a “fragment” instead with the html._ function:

from reactpy import component, html, run


@component
def MyTodoList():
    return html._(
        html.h1("My Todo List"),
        html.img({"src": "https://picsum.photos/id/0/500/200"}),
        html.ul(html.li("The first thing I need to do is...")),
    )


run(MyTodoList)

Fragments allow you to group elements together without leaving any trace in the UI. For example, the first code sample written with ReactPy will produce the second HTML code block:

from reactpy import html

html.ul(
    html._(
        html.li("Group 1 Item 1"),
        html.li("Group 1 Item 2"),
        html.li("Group 1 Item 3"),
    ),
    html._(
        html.li("Group 2 Item 1"),
        html.li("Group 2 Item 2"),
        html.li("Group 2 Item 3"),
    )
)
<ul>
  <li>Group 1 Item 1</li>
  <li>Group 1 Item 2</li>
  <li>Group 1 Item 3</li>
  <li>Group 2 Item 1</li>
  <li>Group 2 Item 2</li>
  <li>Group 2 Item 3</li>
</ul>

Parametrizing Components#

Since components are just regular functions, you can add parameters to them. This allows parent components to pass information to child components. Where standard HTML elements are parametrized by dictionaries, since components behave like typical functions you can give them positional and keyword arguments as you would normally:

from reactpy import component, html, run


@component
def Photo(alt_text, image_id):
    return html.img(
        {
            "src": f"https://picsum.photos/id/{image_id}/500/200",
            "style": {"width": "50%"},
            "alt": alt_text,
        }
    )


@component
def Gallery():
    return html.section(
        html.h1("Photo Gallery"),
        Photo("Landscape", image_id=830),
        Photo("City", image_id=274),
        Photo("Puppy", image_id=237),
    )


run(Gallery)

Conditional Rendering#

Your components will often need to display different things depending on different conditions. Let’s imagine that we had a basic todo list where only some of the items have been completed. Below we have a basic implementation for such a list except that the Item component doesn’t change based on whether it’s done:

from reactpy import component, html, run


@component
def Item(name, done):
    return html.li(name)


@component
def TodoList():
    return html.section(
        html.h1("My Todo List"),
        html.ul(
            Item("Find a cool problem to solve", done=True),
            Item("Build an app to solve it", done=True),
            Item("Share that app with the world!", done=False),
        ),
    )


run(TodoList)

Let’s imagine that we want to add a ✔ to the items which have been marked done=True. One way to do this might be to write an if statement where we return one li element if the item is done and a different one if it’s not:

from reactpy import component, html, run


@component
def Item(name, done):
    if done:
        return html.li(name, " ✔")
    else:
        return html.li(name)


@component
def TodoList():
    return html.section(
        html.h1("My Todo List"),
        html.ul(
            Item("Find a cool problem to solve", done=True),
            Item("Build an app to solve it", done=True),
            Item("Share that app with the world!", done=False),
        ),
    )


run(TodoList)

As you can see this accomplishes our goal! However, notice how similar html.li(name, " ✔") and html.li(name) are. While in this case it isn’t especially harmful, we could make our code a little easier to read and maintain by using an “inline” if statement.

from reactpy import component, html, run


@component
def Item(name, done):
    return html.li(name, " ✔" if done else "")


@component
def TodoList():
    return html.section(
        html.h1("My Todo List"),
        html.ul(
            Item("Find a cool problem to solve", done=True),
            Item("Build an app to solve it", done=True),
            Item("Share that app with the world!", done=False),
        ),
    )


run(TodoList)