Rendering Lists
Overview¶
You will often want to display multiple similar components from a collection of data. You can use Python's list comprehensions to manipulate a list of data. On this page, you'll use comprehensions with ReactPy to filter and transform your data into lists of components.
You will learn
- How to render components from a list using a comprehension
- How to render only specific components using a filtering condition
- When and why to use ReactPy keys
Rendering data from lists¶
Say that you have a list of content.
html.ul(
html.li("Creola Katherine Johnson: mathematician"),
html.li("Mario José Molina-Pasquel Henríquez: chemist"),
html.li("Mohammad Abdus Salam: physicist"),
html.li("Percy Lavon Julian: chemist"),
html.li("Subrahmanyan Chandrasekhar: astrophysicist"),
)
The only difference among those list items is their contents, their data. You will often need to show several instances of the same component using different data when building interfaces: from lists of comments to galleries of profile images. In these situations, you can store that data in Python dictionaries and lists and use comprehensions to render lists of components from them.
Here's a short example of how to generate a list of items from a list:
- Move the data into a list:
people = [
"Creola Katherine Johnson: mathematician",
"Mario José Molina-Pasquel Henríquez: chemist",
"Mohammad Abdus Salam: physicist",
"Percy Lavon Julian: chemist",
"Subrahmanyan Chandrasekhar: astrophysicist",
]
- Build a new list of VDOM nodes from
people:
list_items = [html.li(person) for person in people]
- Return
list_itemsfrom your component wrapped in a<ul>:
return html.ul(list_items)
Here is the result:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
1 2 3 | |
1 | |
If you run this with REACTPY_DEBUG=1, ReactPy prints a warning for each child:
Key not specified for child in list row(7f2c11ac75e0, person={...})
You'll learn how to fix this warning later on this page. Before we get to that, let's add some structure to your data.
Filtering lists of items¶
This data can be structured even more. Rather than strings, each person becomes a dictionary:
people = [
{
"id": 0,
"name": "Creola Katherine Johnson",
"profession": "mathematician",
"accomplishment": "spaceflight calculations",
"image_id": "MK3eW3A",
},
# ...
]
Say you want to display only the chemists. In JavaScript React you would chain filter() and map(); in Python a single comprehension with an if clause does both:
chemists = [person for person in people if person["profession"] == "chemist"]
list_items = [html.li(person["name"]) for person in chemists]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
1 | |
A helper that forgets return renders nothing
The JavaScript "=> with a block body needs a return" trap has a direct Python twin that is worse, because Python fails silently. If a helper used inside a comprehension forgets its return, it evaluates to None — and ReactPy skips None children instead of raising:
def render_person(person): # WRONG: no return
html.li(person["name"])
list_items = [render_person(person) for person in people]
list_items becomes a list of None values and the <ul> renders empty, with no error. Use a comprehension directly when the expression is simple, and add a -> VdomDict return annotation to helpers so type checkers catch the omission.
Keeping list items in order with key¶
When you render children from a list, ReactPy needs a way to tell which child is which. Give each item a key — a string or a number that uniquely identifies it among the other items in that list. Unlike React, where key is a special JSX attribute, in ReactPy it lives in the attributes dictionary like any other attribute:
html.li({"key": person["id"]}, ...)
Children built from a list always need keys
Any child that comes out of a comprehension — or any other list — should carry a key. ReactPy does not warn about this by default; the check is part of its debug mode, so run your app with REACTPY_DEBUG=1 (or set reactpy.config.REACTPY_DEBUG.current = True) while developing to surface them.
Keys tell ReactPy which list item each component corresponds to, so that it can match them up later. This becomes important if your list items can move (e.g. due to sorting), get inserted, or get deleted. A well-chosen key helps ReactPy infer what exactly has happened, and make the correct updates to the DOM tree.
Rather than generating keys on the fly, you should include them in your data:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
1 | |
Displaying several DOM nodes for each list item¶
What do you do when each item needs to render not one, but several DOM nodes?
A plain html(...) fragment takes no positional attributes, but it does accept an attributes dictionary — so pass the key there:
[
html(
{"key": person["id"]},
html.h1(person["name"]),
html.p(person["profession"]),
)
for person in people
]
1 2 3 4 5 6 7 8 9 10 11 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | |
1 | |
Fragments disappear from the DOM, so this produces a flat list of <h1>, <p>, <h1>, <p>, and so on.
Where to get your key¶
Different sources of data provide different sources of keys:
- Data from a database: If your data is coming from a database, you can use the database keys/IDs, which are unique by nature.
- Locally generated data: If your data is generated and persisted locally (e.g. notes in a note-taking app), use an incrementing counter from
itertools.countor a UUID fromuuid.uuid4when creating the item — never while rendering.
Rules of keys¶
- Keys must be unique among siblings. However, it's okay to use the same keys for nodes in different lists.
- Keys must not change or that defeats their purpose! Don't generate them while rendering.
Duplicate keys are a hard error in ReactPy
React logs a warning for duplicate keys and carries on. ReactPy raises instead:
ValueError: Duplicate keys [1] at '/children/0'
The whole component fails to render, so a data set with repeated IDs takes down the section rather than degrading it. If your data can legitimately repeat — for example, two rows that render the same underlying record — make the key unique among siblings by combining the fields that distinguish them.
Why does ReactPy need keys?¶
Imagine that files on your desktop didn't have names. Instead, you'd refer to them by their order — the first file, the second file, and so on. You could get used to it, but once you delete a file, it would get confusing. The second file would become the first file, the third file would be the second file, and so on.
File names in a folder and keys in a list serve a similar purpose. They let us uniquely identify an item between its siblings. A well-chosen key provides more information than the position within the list. Even if the position changes due to reordering, the key lets ReactPy identify the item throughout its lifetime.
This is observable. Give each row its own use_state seeded from the person it renders, then reverse the list. With keys, every row's state travels with its person. Without keys, the state stays behind in the old slot:
keyed=True
before: shown=ada state=ada#0
after : shown=alan state=alan#2
keyed=False
before: shown=ada state=ada#0
after : shown=alan state=ada#0 # the row for "alan" inherited ada's state
Index-based keys are a trap
You might be tempted to use an item's index in the list as its key. In fact, that's effectively what ReactPy uses if you don't specify a key at all. But the order in which you render items will change over time if an item is inserted, deleted, or if the list gets reordered, and — as the output above shows — state then attaches to the wrong item.
Similarly, do not generate keys on the fly, e.g. with key=uuid.uuid4() or key=id(person) inside the render. Keys that differ on every render never match up, so every component and DOM node is recreated each time. Not only is this slow, it will also lose any user input inside the list items. Instead, use a stable ID that comes from the data.
Note that your component never receives key as a prop — ReactPy reserves it. Declaring a key parameter raises immediately:
TypeError: Component render function <function row ...> uses reserved parameter 'key'
If your component needs an ID, pass it as a separate prop: profile(id=id, key=id).
Pass a list, not a generator
html.ul(html.li(person) for person in people) renders, but a generator has no length, so ReactPy cannot verify key integrity and warns about it in debug mode. Bracket the comprehension so the children are a real list: html.ul([html.li(person) for person in people]).
Recap¶
On this page you learned:
- How to move data out of components and into data structures like lists and dictionaries.
- How to generate sets of similar components with a list comprehension.
- How to create filtered lists with an
ifclause in a comprehension. - Why and how to set
keyon each component in a collection so ReactPy can keep track of each of them even if their position or data changes. - That ReactPy checks keys only in debug mode, treats duplicate keys as an error, and reserves
keyso components can never receive it as a prop.