Passing Props to a Component
Overview¶
ReactPy components use props to communicate with each other. Every parent component can pass some information to its child components by giving them props. Props might remind you of HTML attributes, but you can pass any Python value through them, including dictionaries, lists, and functions.
You will learn
- How to pass props to a component
- How to read props from a component
- How to specify default values for props
- How to pass some markup to a component
- How props change over time
Familiar props¶
Props are the information that you pass to a tag. For example, className, src, alt, width, and height are some of the props you can pass to an <img>:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
1 2 3 4 5 6 7 8 | |
1 | |
The props you can pass to an html.img tag are predefined (the browser conforms to the HTML standard), and they keep their familiar camelCase spelling because they are handed straight to the DOM. But you can pass any props to your own components, such as avatar(), to customize them. Here's how!
Passing props to a component¶
In this code, the profile() component isn't passing any props to its child component, avatar():
@component
def profile():
return avatar()
You can give avatar() some props in two steps.
Step 1: Pass props to the child component¶
First, pass some props to avatar(). For example, let's pass two props: person (a dictionary), and size (a number):
@component
def profile():
return html.div(
avatar(
person={"name": "Lin Lanying", "image_id": "1bX5QH6"},
size=100,
),
)
Unlike an HTML tag, a component you write takes its props as ordinary Python arguments. There is no attributes dictionary here — the values land directly in the function's parameters.
Step 2: Read props inside the child component¶
You read props by listing their names person, size directly after def avatar. This lets you use them inside the avatar() code, like you would with a variable.
@component
def avatar(person, size):
# person and size are available here
...
Add some logic to avatar() that uses the person and size props for rendering, and you're done.
Now you can configure avatar() to render in many different ways with different props. Try tweaking the values!
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 | |
1 2 3 4 5 6 7 8 | |
1 | |
Props let you think about parent and child components independently. For example, you can change the person or the size props inside profile() without having to think about how avatar() uses them. Similarly, you can change how avatar() uses these props, without looking at profile().
You can think of props like "knobs" that you can adjust. They serve the same role as arguments serve for functions—in fact, props are the only argument to your component! A component is nothing but a Python function, and a function can only accept arguments.
Every argument is a prop
In JavaScript, a component receives a single props object which you then destructure into function Avatar({ person, size }). Python needs no such ceremony: the parameters are the props.
@component
def avatar(person, size): ...
is already the direct, Pythonic form of that idea. But it means every parameter you declare is a prop a caller can pass by name — so keep the signature of a component to the values it actually renders.
Specifying a default value for a prop¶
If you want to give a prop a default value to fall back on when no value is specified, put = and the default value right after the parameter:
@component
def avatar(person, size=100):
# ...
Now, if avatar(person=...) is rendered with no size prop, the size will be set to 100.
The default value is used only when the caller omits the argument entirely. If you pass size=None or size=0, the default value will not be used — Python applies defaults for missing arguments, not for "empty" ones.
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 | |
1 2 3 4 5 6 7 8 | |
1 | |
Forwarding props with keyword argument unpacking¶
Sometimes, passing props gets very repetitive:
@component
def profile(person, size, is_sepia, thick_border):
return html.div(
{"className": "card"},
avatar(
person=person,
size=size,
is_sepia=is_sepia,
thick_border=thick_border,
),
)
There's nothing wrong with repetitive code—it can be more legible. But at times you may value conciseness. Some components forward all of their props to their children, like this profile() does with avatar(). Because they don't use any of their props directly, it can make sense to use Python's keyword argument unpacking instead:
@component
def profile(**props):
return html.div({"className": "card"}, avatar(**props))
This forwards all of profile()'s props to the avatar() without listing each of their names. Inside the function, props is a plain dictionary, so you can also merge or filter it before forwarding.
Use unpacking with restraint. If you're using it in every other component, something is wrong. It hides the component's real interface, so neither your editor nor a reader can tell which props it accepts. Often, it indicates that you should split your components and pass children as arguments. More on that next!
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 | |
1 2 3 4 5 6 7 8 | |
1 | |
Passing children to a component¶
It is common to nest built-in browser tags:
html.div(html.img())
Sometimes you'll want to nest your own components the same way:
card(avatar())
Anything you pass positionally lands in a *args parameter of the parent component, which is the ReactPy equivalent of React's children prop. For example, the card() component below collects its children and renders them inside a wrapper <div>:
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 | |
1 2 3 4 5 6 7 8 | |
1 | |
Try replacing the avatar() inside card() with some text to see how the card() component can wrap any nested content. It doesn't need to "know" what's being rendered inside of it. You will see this flexible pattern in many places.
You can think of a component with a *children parameter as having a "hole" that can be "filled in" by its parent components with arbitrary markup. You will often use children for visual wrappers: panels, grids, etc.
Keyword-only children
Because positional arguments become children, a component that accepts both should make its props keyword-only to avoid an ambiguous signature:
@component
def card(*children, title=None):
return html.div({"className": "card"}, title, *children)
Now title= can never be mistaken for a child.
How props change over time¶
The clock() component below receives two props from its parent component: color and time.
Try changing the color in the select box below:
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 | |
1 | |
This example illustrates that a component may receive different props over time. Props are not always static! Here, the time prop changes every second, and the color prop changes when you select another color. Props reflect a component's data at any point in time, rather than only in the beginning.
However, props are immutable—a term from computer science meaning "unchangeable". When a component needs to change its props (for example, in response to a user interaction or new data), it will have to "ask" its parent component to pass it different props—a new set of arguments! ReactPy re-renders the child with those new values rather than editing the old ones in place.
Don't try to change props
When you need to respond to user input (like changing the selected color), you will need to "set state", which you can learn about in State: A Component's Memory. Assigning to a prop parameter inside a component only rebinds the local name; the parent's value is untouched, and the next render overwrites it anyway.
Recap¶
- To pass props, add them as arguments to the component call, just like you would with an HTML tag's attributes.
- To read props, list them as parameters:
def avatar(person, size). - You can specify a default value like
size=100, which is used when the caller omits the argument. - You can forward all props with
def profile(**props)andavatar(**props), but don't overuse it! - Nested children like
card(avatar())arrive as a*childrenparameter. - Props are read-only snapshots in time: every render receives a new version of props.
- You can't change props. When you need interactivity, you'll need to set state.