Skip to content

Commit 023cb15

Browse files
authored
Single Page Application (SPA) compatibility via reactpy-router (#185)
Port reactpy-router into a Django equivalent (using Django's URL matching schema)
1 parent 6fb7ba2 commit 023cb15

File tree

24 files changed

+293
-37
lines changed

24 files changed

+293
-37
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ Using the following categories, list your changes in this order:
3636

3737
### Added
3838

39+
- Built-in Single Page Application (SPA) support!
40+
- `reactpy_django.router.django_router` can be used to render your Django application as a SPA.
3941
- SEO compatible rendering!
4042
- `settings.py:REACTPY_PRERENDER` can be set to `True` to make components pre-render by default.
4143
- Or, you can enable it on individual components via the template tag: `{% component "..." prerender="True" %}`.

docs/python/django-router.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from reactpy import component, html
2+
from reactpy_django.router import django_router
3+
from reactpy_router import route
4+
5+
6+
@component
7+
def my_component():
8+
return django_router(
9+
route("/router/", html.div("Example 1")),
10+
route("/router/any/<value>/", html.div("Example 2")),
11+
route("/router/integer/<int:value>/", html.div("Example 3")),
12+
route("/router/path/<path:value>/", html.div("Example 4")),
13+
route("/router/slug/<slug:value>/", html.div("Example 5")),
14+
route("/router/string/<str:value>/", html.div("Example 6")),
15+
route("/router/uuid/<uuid:value>/", html.div("Example 7")),
16+
route("/router/two_values/<int:value>/<str:value2>/", html.div("Example 9")),
17+
)

docs/python/use-location.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
def my_component():
77
location = use_location()
88

9-
return html.div(str(location))
9+
return html.div(location.pathname + location.search)

docs/src/reference/hooks.md

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -366,9 +366,7 @@ Shortcut that returns the WebSocket or HTTP connection's [scope](https://channel
366366

367367
### `#!python use_location()`
368368

369-
Shortcut that returns the WebSocket or HTTP connection's URL `#!python path`.
370-
371-
You can expect this hook to provide strings such as `/reactpy/my_path`.
369+
Shortcut that returns the browser's current `#!python Location`.
372370

373371
=== "components.py"
374372

@@ -388,14 +386,6 @@ You can expect this hook to provide strings such as `/reactpy/my_path`.
388386
| --- | --- |
389387
| `#!python Location` | An object containing the current URL's `#!python pathname` and `#!python search` query. |
390388

391-
??? info "This hook's behavior will be changed in a future update"
392-
393-
This hook will be updated to return the browser's currently active HTTP path. This change will come in alongside ReactPy URL routing support.
394-
395-
Check out [reactive-python/reactpy-django#147](https://github.com/reactive-python/reactpy-django/issues/147) for more information.
396-
397-
---
398-
399389
### `#!python use_origin()`
400390

401391
Shortcut that returns the WebSocket or HTTP connection's `#!python origin`.

docs/src/reference/router.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
## Overview
2+
3+
<p class="intro" markdown>
4+
5+
A variant of [`reactpy-router`](https://github.com/reactive-python/reactpy-router) that utilizes Django conventions.
6+
7+
</p>
8+
9+
!!! abstract "Note"
10+
11+
Looking for more details on URL routing?
12+
13+
This package only contains Django specific URL routing features. Standard features can be found within [`reactive-python/reactpy-router`](https://reactive-python.github.io/reactpy-router/).
14+
15+
---
16+
17+
## `#!python django_router(*routes)`
18+
19+
=== "components.py"
20+
21+
```python
22+
{% include "../../python/django-router.py" %}
23+
```
24+
25+
??? example "See Interface"
26+
27+
<font size="4">**Parameters**</font>
28+
29+
| Name | Type | Description | Default |
30+
| --- | --- | --- | --- |
31+
| `#!python *routes` | `#!python Route` | An object from `reactpy-router` containing a `#!python path`, `#!python element`, and child `#!python *routes`. | N/A |
32+
33+
<font size="4">**Returns**</font>
34+
35+
| Type | Description |
36+
| --- | --- |
37+
| `#!python VdomDict | None` | The matched component/path after it has been fully rendered. |
38+
39+
??? question "How is this different from `#!python reactpy_router.simple.router`?"
40+
41+
This component utilizes `reactpy-router` under the hood, but provides a more Django-like URL routing syntax.

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ nav:
77
- Reference:
88
- Components: reference/components.md
99
- Hooks: reference/hooks.md
10+
- URL Router: reference/router.md
1011
- Decorators: reference/decorators.md
1112
- Utilities: reference/utils.md
1213
- Template Tag: reference/template-tag.md

requirements/pkg-deps.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
channels >=4.0.0
22
django >=4.2.0
33
reactpy >=1.0.2, <1.1.0
4+
reactpy-router >=0.1.1, <1.0.0
45
aiofile >=3.0
56
dill >=0.3.5
67
orjson >=3.6.0

src/js/src/client.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { BaseReactPyClient, ReactPyClient, ReactPyModule } from "@reactpy/client";
1+
import {
2+
BaseReactPyClient,
3+
ReactPyClient,
4+
ReactPyModule,
5+
} from "@reactpy/client";
26
import { createReconnectingWebSocket } from "./utils";
37
import { ReactPyDjangoClientProps, ReactPyUrls } from "./types";
48

src/js/src/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,17 @@ export function mountComponent(
3939
}
4040
}
4141

42+
// Embed the initial HTTP path into the WebSocket URL
43+
let componentUrl = new URL(`${wsOrigin}/${urlPrefix}/${componentPath}`);
44+
componentUrl.searchParams.append("http_pathname", window.location.pathname);
45+
if (window.location.search) {
46+
componentUrl.searchParams.append("http_search", window.location.search);
47+
}
48+
4249
// Configure a new ReactPy client
4350
const client = new ReactPyDjangoClient({
4451
urls: {
45-
componentUrl: `${wsOrigin}/${urlPrefix}/${componentPath}`,
52+
componentUrl: componentUrl,
4653
query: document.location.search,
4754
jsModules: `${httpOrigin}/${jsModulesPath}`,
4855
},

src/js/src/types.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
export type ReconnectOptions = {
2-
startInterval: number;
3-
maxInterval: number;
4-
maxRetries: number;
5-
backoffMultiplier: number;
6-
}
2+
startInterval: number;
3+
maxInterval: number;
4+
maxRetries: number;
5+
backoffMultiplier: number;
6+
};
77

88
export type ReactPyUrls = {
9-
componentUrl: string;
10-
query: string;
11-
jsModules: string;
12-
}
9+
componentUrl: URL;
10+
query: string;
11+
jsModules: string;
12+
};
1313

1414
export type ReactPyDjangoClientProps = {
15-
urls: ReactPyUrls;
16-
reconnectOptions: ReconnectOptions;
17-
}
15+
urls: ReactPyUrls;
16+
reconnectOptions: ReconnectOptions;
17+
};

0 commit comments

Comments
 (0)