Skip to content

Your First Routed Application

Here you'll learn the various features of reactpy-router and how to use them. These examples will utilize the reactpy_router.browser_router.

Note

These docs assume you already know the basics of ReactPy.


Let's build a simple web application for viewing messages between several people.

For the purposes of this tutorial we'll be working with the following data.

message_data = [
    {"id": 1, "with": ["Alice"], "from": None, "message": "Hello!"},
    {"id": 2, "with": ["Alice"], "from": "Alice", "message": "How's it going?"},
    {"id": 3, "with": ["Alice"], "from": None, "message": "Good, you?"},
    {"id": 4, "with": ["Alice"], "from": "Alice", "message": "Good, thanks!"},
    {"id": 5, "with": ["Alice", "Bob"], "from": None, "message": "We meeting now?"},
    {"id": 6, "with": ["Alice", "Bob"], "from": "Alice", "message": "Not sure."},
    {"id": 7, "with": ["Alice", "Bob"], "from": "Bob", "message": "I'm here!"},
    {"id": 8, "with": ["Alice", "Bob"], "from": None, "message": "Great!"},
]

In a more realistic application this data would be stored in a database, but for this tutorial we'll just keep it in memory.

Creating Basic Routes

The first step is to create a basic router that will display the home page when the user navigates to the root of the application, and a "missing link" page for any other route.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from reactpy import component, html, run

from reactpy_router import browser_router, route


@component
def root():
    return browser_router(
        route("/", html.h1("Home Page 🏠")),
        route("{404:any}", html.h1("Missing Link 🔗‍💥")),
    )


run(root)

When navigating to http://127.0.0.1:8000 you should see Home Page 🏠. However, if you go to any other route you will instead see Missing Link 🔗‍💥.

With this foundation you can start adding more routes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from reactpy import component, html, run

from reactpy_router import browser_router, route


@component
def root():
    return browser_router(
        route("/", html.h1("Home Page 🏠")),
        route("/messages", html.h1("Messages 💬")),
        route("{404:any}", html.h1("Missing Link 🔗‍💥")),
    )


run(root)

With this change you can now also go to /messages to see Messages 💬.

Instead of using the standard reactpy.html.a element to create links to different parts of your application, use reactpy_router.link instead. When users click links constructed using reactpy_router.link, ReactPy will handle the transition and prevent a full page reload.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from reactpy import component, html, run

from reactpy_router import browser_router, link, route


@component
def root():
    return browser_router(
        route("/", home()),
        route("/messages", html.h1("Messages 💬")),
        route("{404:any}", html.h1("Missing Link 🔗‍💥")),
    )


@component
def home():
    return html.div(
        html.h1("Home Page 🏠"),
        link({"to": "/messages"}, "Messages"),
    )


run(root)

Now, when you go to the home page, you can click Messages link to go to /messages.

Adding Nested Routes

Routes can be nested in order to construct more complicated application structures.

 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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import operator
from typing import TypedDict

from reactpy import component, html, run

from reactpy_router import browser_router, link, route

message_data: list["MessageDataType"] = [
    {"id": 1, "with": ["Alice"], "from": None, "message": "Hello!"},
    {"id": 2, "with": ["Alice"], "from": "Alice", "message": "How's it going?"},
    {"id": 3, "with": ["Alice"], "from": None, "message": "Good, you?"},
    {"id": 4, "with": ["Alice"], "from": "Alice", "message": "Good, thanks!"},
    {"id": 5, "with": ["Alice", "Bob"], "from": None, "message": "We meeting now?"},
    {"id": 6, "with": ["Alice", "Bob"], "from": "Alice", "message": "Not sure."},
    {"id": 7, "with": ["Alice", "Bob"], "from": "Bob", "message": "I'm here!"},
    {"id": 8, "with": ["Alice", "Bob"], "from": None, "message": "Great!"},
]


@component
def root():
    return browser_router(
        route("/", home()),
        route(
            "/messages",
            all_messages(),
            # we'll improve upon these manually created routes in the next section...
            route("/with/Alice", messages_with("Alice")),
            route("/with/Alice-Bob", messages_with("Alice", "Bob")),
        ),
        route("{404:any}", html.h1("Missing Link 🔗‍💥")),
    )


@component
def home():
    return html.div(
        html.h1("Home Page 🏠"),
        link({"to": "/messages"}, "Messages"),
    )


@component
def all_messages():
    last_messages = {", ".join(msg["with"]): msg for msg in sorted(message_data, key=operator.itemgetter("id"))}

    messages = []
    for msg in last_messages.values():
        _link = link(
            {"to": f"/messages/with/{'-'.join(msg['with'])}"},
            f"Conversation with: {', '.join(msg['with'])}",
        )
        msg_from = f"{'' if msg['from'] is None else '🔴'} {msg['message']}"
        messages.append(html.li({"key": msg["id"]}, html.p(_link), msg_from))

    return html.div(
        html.h1("All Messages 💬"),
        html.ul(messages),
    )


@component
def messages_with(*names):
    messages = [msg for msg in message_data if tuple(msg["with"]) == names]
    return html.div(
        html.h1(f"Messages with {', '.join(names)} 💬"),
        html.ul([
            html.li(
                {"key": msg["id"]},
                f"{msg['from'] or 'You'}: {msg['message']}",
            )
            for msg in messages
        ]),
    )


run(root)

MessageDataType = TypedDict(
    "MessageDataType",
    {"id": int, "with": list[str], "from": str | None, "message": str},
)

Adding Route Parameters

In the example above we had to manually create a messages_with(...) component for each conversation. This would be better accomplished by defining a single route that declares route parameters instead.

Any parameters that have matched in the currently displayed route can then be consumed with the use_params hook which returns a dictionary mapping the parameter names to their values. Note that parameters with a declared type will be converted to is in the parameters dictionary. So for example /my/route/{my_param:float} would match /my/route/3.14 and have a parameter dictionary of {"my_param": 3.14}.

If we take this information and apply it to our growing example application we'd substitute the manually constructed /messages/with routes with a single /messages/with/{names} route.

 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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import operator
from typing import TypedDict

from reactpy import component, html, run

from reactpy_router import browser_router, link, route, use_params

message_data: list["MessageDataType"] = [
    {"id": 1, "with": ["Alice"], "from": None, "message": "Hello!"},
    {"id": 2, "with": ["Alice"], "from": "Alice", "message": "How's it going?"},
    {"id": 3, "with": ["Alice"], "from": None, "message": "Good, you?"},
    {"id": 4, "with": ["Alice"], "from": "Alice", "message": "Good, thanks!"},
    {"id": 5, "with": ["Alice", "Bob"], "from": None, "message": "We meeting now?"},
    {"id": 6, "with": ["Alice", "Bob"], "from": "Alice", "message": "Not sure."},
    {"id": 7, "with": ["Alice", "Bob"], "from": "Bob", "message": "I'm here!"},
    {"id": 8, "with": ["Alice", "Bob"], "from": None, "message": "Great!"},
]


@component
def root():
    return browser_router(
        route("/", home()),
        route(
            "/messages",
            all_messages(),
            route("/with/{names}", messages_with()),  # note the path param
        ),
        route("{404:any}", html.h1("Missing Link 🔗‍💥")),
    )


@component
def home():
    return html.div(
        html.h1("Home Page 🏠"),
        link({"to": "/messages"}, "Messages"),
    )


@component
def all_messages():
    last_messages = {", ".join(msg["with"]): msg for msg in sorted(message_data, key=operator.itemgetter("id"))}
    messages = []
    for msg in last_messages.values():
        msg_hyperlink = link(
            {"to": f"/messages/with/{'-'.join(msg['with'])}"},
            f"Conversation with: {', '.join(msg['with'])}",
        )
        msg_from = f"{'' if msg['from'] is None else '🔴'} {msg['message']}"
        messages.append(html.li({"key": msg["id"]}, html.p(msg_hyperlink), msg_from))

    return html.div(
        html.h1("All Messages 💬"),
        html.ul(messages),
    )


@component
def messages_with():
    names = tuple(use_params()["names"].split("-"))  # and here we use the path param
    messages = [msg for msg in message_data if tuple(msg["with"]) == names]
    return html.div(
        html.h1(f"Messages with {', '.join(names)} 💬"),
        html.ul([
            html.li(
                {"key": msg["id"]},
                f"{msg['from'] or 'You'}: {msg['message']}",
            )
            for msg in messages
        ]),
    )


run(root)

MessageDataType = TypedDict(
    "MessageDataType",
    {"id": int, "with": list[str], "from": str | None, "message": str},
)

Last update: October 24, 2024
Authors: Mark Bakhit