Rendering Data#

Frequently you need to construct a number of similar components from a collection of data. Let’s imagine that we want to create a todo list that can be ordered and filtered on the priority of each item in the list. To start, we’ll take a look at the kind of view we’d like to display:

<ul>
    <li>Make breakfast (important)</li>
    <li>Feed the dog (important)</li>
    <li>Do laundry</li>
    <li>Go on a run (important)</li>
    <li>Clean the house</li>
    <li>Go to the grocery store</li>
    <li>Do some coding</li>
    <li>Read a book (important)</li>
</ul>

Based on this, our next step in achieving our goal is to break this view down into the underlying data that we’d want to use to represent it. The most straightforward way to do this would be to just put the text of each <li> into a list:

tasks = [
    "Make breakfast (important)",
    "Feed the dog (important)",
    "Do laundry",
    "Go on a run (important)",
    "Clean the house",
    "Go to the grocery store",
    "Do some coding",
    "Read a book (important)",
]

We could then take this list and “render” it into a series of <li> elements:

from reactpy import html

list_item_elements = [html.li(text) for text in tasks]

This list of elements can then be passed into a parent <ul> element:

list_element = html.ul(list_item_elements)

The last thing we have to do is return this from a component:

from reactpy import component, html, run


@component
def DataList(items):
    list_item_elements = [html.li(text) for text in items]
    return html.ul(list_item_elements)


@component
def TodoList():
    tasks = [
        "Make breakfast (important)",
        "Feed the dog (important)",
        "Do laundry",
        "Go on a run (important)",
        "Clean the house",
        "Go to the grocery store",
        "Do some coding",
        "Read a book (important)",
    ]
    return html.section(
        html.h1("My Todo List"),
        DataList(tasks),
    )


run(TodoList)

Filtering and Sorting Elements#

Our representation of tasks worked fine to just get them on the screen, but it doesn’t extend well to the case where we want to filter and order them based on priority. Thus, we need to change the data structure we’re using to represent our tasks:

tasks = [
    {"text": "Make breakfast", "priority": 0},
    {"text": "Feed the dog", "priority": 0},
    {"text": "Do laundry", "priority": 2},
    {"text": "Go on a run", "priority": 1},
    {"text": "Clean the house", "priority": 2},
    {"text": "Go to the grocery store", "priority": 2},
    {"text": "Do some coding", "priority": 1},
    {"text": "Read a book", "priority": 1},
]

With this we can now imaging writing some filtering and sorting logic using Python’s filter() and sorted() functions respectively. We’ll do this by only displaying items whose priority is less than or equal to some filter_by_priority and then ordering the elements based on the priority:

filter_by_priority = 1
sort_by_priority = True

filtered_tasks = tasks
if filter_by_priority is not None:
    filtered_tasks = [t for t in filtered_tasks if t["priority"] <= filter_by_priority]
if sort_by_priority:
    filtered_tasks = list(sorted(filtered_tasks, key=lambda t: t["priority"]))

assert filtered_tasks == [
    {'text': 'Make breakfast', 'priority': 0},
    {'text': 'Feed the dog', 'priority': 0},
    {'text': 'Go on a run', 'priority': 1},
    {'text': 'Do some coding', 'priority': 1},
    {'text': 'Read a book', 'priority': 1},
]

We could then add this code to our DataList component:

Warning

The code below produces a bunch of warnings! Be sure to read the next section to find out why.

from reactpy import component, html, run


@component
def DataList(items, filter_by_priority=None, sort_by_priority=False):
    if filter_by_priority is not None:
        items = [i for i in items if i["priority"] <= filter_by_priority]
    if sort_by_priority:
        items = sorted(items, key=lambda i: i["priority"])
    list_item_elements = [html.li(i["text"]) for i in items]
    return html.ul(list_item_elements)


@component
def TodoList():
    tasks = [
        {"text": "Make breakfast", "priority": 0},
        {"text": "Feed the dog", "priority": 0},
        {"text": "Do laundry", "priority": 2},
        {"text": "Go on a run", "priority": 1},
        {"text": "Clean the house", "priority": 2},
        {"text": "Go to the grocery store", "priority": 2},
        {"text": "Do some coding", "priority": 1},
        {"text": "Read a book", "priority": 1},
    ]
    return html.section(
        html.h1("My Todo List"),
        DataList(tasks, filter_by_priority=1, sort_by_priority=True),
    )


run(TodoList)

Organizing Items With Keys#

If you run the examples above in debug mode you’ll see the server log a bunch of errors that look something like:

Key not specified for child in list {'tagName': 'li', 'children': ...}

What this is telling us is that we haven’t specified a unique key for each of the items in our todo list. In order to silence this warning we need to expand our data structure even further to include a unique ID for each item in our todo list:

tasks = [
    {"id": 0, "text": "Make breakfast", "priority": 0},
    {"id": 1, "text": "Feed the dog", "priority": 0},
    {"id": 2, "text": "Do laundry", "priority": 2},
    {"id": 3, "text": "Go on a run", "priority": 1},
    {"id": 4, "text": "Clean the house", "priority": 2},
    {"id": 5, "text": "Go to the grocery store", "priority": 2},
    {"id": 6, "text": "Do some coding", "priority": 1},
    {"id": 7, "text": "Read a book", "priority": 1},
]

Then, as we’re constructing our <li> elements we’ll declare a key attribute:

list_item_elements = [html.li({"key": t["id"]}, t["text"]) for t in tasks]

This key tells ReactPy which <li> element corresponds to which item of data in our tasks list. This becomes important if the order or number of items in your list can change. In our case, if we decided to change whether we want to filter_by_priority or sort_by_priority the items in our <ul> element would change. Given this, here’s how we’d change our component:

from reactpy import component, html, run


@component
def DataList(items, filter_by_priority=None, sort_by_priority=False):
    if filter_by_priority is not None:
        items = [i for i in items if i["priority"] <= filter_by_priority]
    if sort_by_priority:
        items = sorted(items, key=lambda i: i["priority"])
    list_item_elements = [html.li({"key": i["id"]}, i["text"]) for i in items]
    return html.ul(list_item_elements)


@component
def TodoList():
    tasks = [
        {"id": 0, "text": "Make breakfast", "priority": 0},
        {"id": 1, "text": "Feed the dog", "priority": 0},
        {"id": 2, "text": "Do laundry", "priority": 2},
        {"id": 3, "text": "Go on a run", "priority": 1},
        {"id": 4, "text": "Clean the house", "priority": 2},
        {"id": 5, "text": "Go to the grocery store", "priority": 2},
        {"id": 6, "text": "Do some coding", "priority": 1},
        {"id": 7, "text": "Read a book", "priority": 1},
    ]
    return html.section(
        html.h1("My Todo List"),
        DataList(tasks, filter_by_priority=1, sort_by_priority=True),
    )


run(TodoList)

Keys for Components#

Thus far we’ve been talking about passing keys to standard HTML elements. However, this principle also applies to components too. Every function decorated with the @component decorator automatically gets a key parameter that operates in the exact same way that it does for standard HTML elements:

from reactpy import component


@component
def ListItem(text):
    return html.li(text)

tasks = [
    {"id": 0, "text": "Make breakfast"},
    {"id": 1, "text": "Feed the dog"},
    {"id": 2, "text": "Do laundry"},
    {"id": 3, "text": "Go on a run"},
    {"id": 4, "text": "Clean the house"},
    {"id": 5, "text": "Go to the grocery store"},
    {"id": 6, "text": "Do some coding"},
    {"id": 7, "text": "Read a book"},
]

list_element = [ListItem(t["text"], key=t["id"]) for t in tasks]

Warning

The key argument is reserved for this purpose. Defining a component with a function that has a key parameter will cause an error:

from reactpy import component

@component
def FunctionWithKeyParam(key):
    ...
Traceback (most recent call last):
...
TypeError: Component render function ... uses reserved parameter 'key'

Rules of Keys#

In order to avoid unexpected behaviors when rendering data with keys, there are a few rules that need to be followed. These will ensure that each item of data is associated with the correct UI element.

Keys may be the same if their elements are not siblings

If two elements have different parents in the UI, they can use the same keys.

data_1 = [
    {"id": 1, "text": "Something"},
    {"id": 2, "text": "Something else"},
]

data_2 = [
    {"id": 1, "text": "Another thing"},
    {"id": 2, "text": "Yet another thing"},
]

html.section(
    html.ul([html.li(data["text"], key=data["id"]) for data in data_1]),
    html.ul([html.li(data["text"], key=data["id"]) for data in data_2]),
)
Keys must be unique amongst siblings

Keys must be unique among siblings.

data = [
    {"id": 1, "text": "Something"},
    {"id": 2, "text": "Something else"},
    {"id": 1, "text": "Another thing"},      # BAD: has a duplicated id
    {"id": 2, "text": "Yet another thing"},  # BAD: has a duplicated id
]

html.section(
    html.ul([html.li(data["text"], key=data["id"]) for data in data]),
)
Keys must be fixed to their data.

Don’t generate random values for keys to avoid the warning.

from random import random

data = [
    {"id": random(), "text": "Something"},
    {"id": random(), "text": "Something else"},
    {"id": random(), "text": "Another thing"},
    {"id": random(), "text": "Yet another thing"},
]

html.section(
    html.ul([html.li(data["text"], key=data["id"]) for data in data]),
)

Doing so will result in unexpected behavior.

Since we’ve just been working with a small amount of sample data thus far, it was easy enough for us to manually add an id key to each item of data. Often though, we have to work with data that already exists. In those cases, how should we pick what value to use for each key?

  • If your data comes from your database you should use the keys and IDs generated by that database since these are inherently unique. For example, you might use the primary key of records in a relational database.

  • If your data is generated and persisted locally (e.g. notes in a note-taking app), use an incrementing counter or uuid from the standard library when creating items.


Read More

Learn about why ReactPy needs keys in the first place.