Conditional Rendering
Overview¶
Your components will often need to display different things depending on different conditions. In ReactPy, you can conditionally render markup using Python syntax like if statements and conditional expressions.
You will learn
- How to return different markup depending on a condition
- How to conditionally include or exclude a piece of markup
- Conditional syntax shortcuts you'll encounter in ReactPy codebases
Conditionally returning markup¶
Let's say you have a packing_list() component rendering several item()s, which can be marked as packed or not. Notice that some of the item() calls have their is_packed prop set to True instead of False. You want to add a checkmark (✔) to packed items if is_packed is True.
You can write this as an if/else statement like so:
if is_packed:
return html.li({"className": "item"}, f"{name} ✔")
return html.li({"className": "item"}, name)
If the is_packed prop is True, this code returns a different VDOM tree. With this change, some of the items get a checkmark at the end:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
1 2 3 | |
1 | |
Try editing what gets returned in either case, and see how the result changes!
Notice how you're creating branching logic with Python's if and return statements. In ReactPy, control flow (like conditions) is handled by Python.
Conditionally returning nothing with None¶
In some situations, you won't want to render anything at all. For example, say you don't want to show packed items at all. A component must return something — in ReactPy, that something is None:
if is_packed:
return None
return html.li({"className": "item"}, name)
If is_packed is true, the component will return nothing, None. Otherwise, it will return markup to render.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
1 2 3 | |
1 | |
In practice, returning None from a component isn't common because it might surprise a developer trying to render it. More often, you would conditionally include or exclude the component in the parent component. Here's how to do that!
Conditionally including markup¶
In the previous example, you controlled which (if any!) VDOM tree would be returned by the component. You may already have noticed some duplication in the render output:
html.li({"className": "item"}, f"{name} ✔")
is very similar to
html.li({"className": "item"}, name)
Both of the conditional branches return html.li({"className": "item"}, ...):
if is_packed:
return html.li({"className": "item"}, f"{name} ✔")
return html.li({"className": "item"}, name)
While this duplication isn't harmful, it could make your code harder to maintain. What if you want to change the className? You'd have to do it in two places in your code! In such a situation, you could conditionally include a little markup to make your code more DRY.
Conditional (ternary) expression¶
Python has a compact syntax for writing a conditional expression — the conditional expression or "ternary operator".
Instead of this:
if is_packed:
return html.li({"className": "item"}, f"{name} ✔")
return html.li({"className": "item"}, name)
You can write this:
return html.li({"className": "item"}, f"{name} ✔" if is_packed else name)
You can read it as "if is_packed is true, then render f"{name} ✔", otherwise render name".
Now let's say you want to wrap the completed item's text into another HTML tag, like <del> to strike it out. You can add even more newlines and parentheses so that it's easier to nest more markup in each of the cases:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
1 2 3 | |
1 | |
This style works well for simple conditions, but use it in moderation. If your components get messy with too much nested conditional markup, consider extracting child components to clean things up. In ReactPy, markup is a part of your code, so you can use tools like variables and functions to tidy up complex expressions.
Are these two examples fully equivalent?
If you're coming from an object-oriented programming background, you might assume that the two examples above are subtly different because one of them may create two different "instances" of html.li. But VDOM dictionaries aren't "instances" because they don't hold any internal state and aren't real DOM nodes. They're lightweight descriptions, like blueprints. So these two examples, in fact, are completely equivalent. Preserving and Resetting State goes into detail about how this works.
Excluding a child with a conditional¶
In JavaScript React you'll often see the logical AND operator used to render a child only when a condition holds:
<li className="item">{name} {isPacked && "✔"}</li>
There is no equivalent of this idiom in ReactPy. Python's and returns one of its operands rather than a renderable-agnostic False, and ReactPy renders False as the literal text "False" instead of skipping it:
# WRONG: renders "Photo of Tam False" when is_packed is False
return html.li({"className": "item"}, name, is_packed and "✔")
Reach for a conditional expression that yields an empty list, and unpack it:
return html.li(
{"className": "item"},
name,
*(["✔"] if is_packed else []),
)
Or build the children list up front, which reads better as the number of optional children grows:
@component
def item(name, is_packed):
content = [name]
if is_packed:
content.append("✔")
return html.li({"className": "item"}, *content)
Don't put a number on the left side of a conditional expression
The classic messageCount && <p>New messages</p> bug has an exact Python twin. and returns the left operand when it is falsy, and ReactPy renders numbers as text — so count and html.p("New messages") renders 0 rather than nothing when count is 0. Make the left side a genuine boolean test:
html.p("New messages") if count > 0 else None
Conditionally assigning markup to a variable¶
When the shortcuts get in the way of writing plain code, try using an if statement and a variable. Start by providing the default content you want to display, the name:
item_content = name
Use an if statement to reassign a VDOM expression to item_content if is_packed is True:
if is_packed:
item_content = html.del_(f"{name} ✔")
Embed the variable in the returned VDOM tree, nesting the previously calculated expression inside the parent element:
html.li({"className": "item"}, item_content)
This style is the most verbose, but it's also the most flexible:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
1 2 3 | |
1 | |
Like before, this works not only for text, but for arbitrary markup too.
Tags that collide with Python keywords
del is a Python keyword, so the <del> tag is spelled with a trailing underscore: html.del_. The underscore is stripped before the tag name is emitted, so it still renders <del>. The same rule covers html.import_, html.object, and friends — and dashes become underscores, so <data-table> is html.data_table.
If you're not familiar with Python, this variety of styles might seem overwhelming at first. However, learning them will help you read and write any Python code — and not just ReactPy components! Pick the one you prefer for a start, and then consult this reference again if you forget how the other ones work.
Recap¶
- To conditionally return different markup from a component, use
ifstatements andreturn. - To render nothing, return
Nonefrom the component. - To tidy up duplicated markup, use a conditional expression:
x if condition else y. - Don't use
andto include a child — ReactPy rendersFalseas the text"False". Unpack an empty list instead. - For anything more complex, assign children to variables with
ifstatements.