diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml
index 0a7ae35a..92f45852 100644
--- a/.github/workflows/test-python.yml
+++ b/.github/workflows/test-python.yml
@@ -12,10 +12,12 @@ on:
jobs:
python-source:
- runs-on: ubuntu-latest
+ runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
+ settings-module: ["single_db", "multi_db"]
+ operating-system: ["ubuntu-latest", "windows-latest"]
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
@@ -28,26 +30,7 @@ jobs:
- name: Install Python Dependencies
run: pip install --upgrade pip hatch uv
- name: Run Single DB Tests
- run: hatch test --python ${{ matrix.python-version }} --ds=test_app.settings_single_db -v
-
- python-source-multi-db:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- python-version: ["3.9", "3.10", "3.11", "3.12"]
- steps:
- - uses: actions/checkout@v4
- - uses: oven-sh/setup-bun@v2
- with:
- bun-version: latest
- - name: Use Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python-version }}
- - name: Install Python Dependencies
- run: pip install --upgrade pip hatch uv
- - name: Run Multi-DB Tests
- run: hatch test --python ${{ matrix.python-version }} --ds=test_app.settings_multi_db -v
+ run: hatch test --python ${{ matrix.python-version }} --ds=test_app.settings_${{matrix.settings-module}} -v
python-formatting:
runs-on: ubuntu-latest
@@ -76,5 +59,5 @@ jobs:
python-version: 3.x
- name: Install Python Dependencies
run: pip install --upgrade pip hatch uv
- - name: Check Python formatting
+ - name: Run Python type checker
run: hatch run python:type_check
diff --git a/.gitignore b/.gitignore
index e3cbc599..e675e09a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
# ReactPy-Django Build Artifacts
-src/reactpy_django/static/reactpy_django/client.js
+src/reactpy_django/static/reactpy_django/index.js
+src/reactpy_django/static/reactpy_django/index.js.map
src/reactpy_django/static/reactpy_django/pyscript
src/reactpy_django/static/reactpy_django/morphdom
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c6fa5cc3..f3a0ff04 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,7 +19,13 @@ Don't forget to remove deprecated code on each major release!
## [Unreleased]
-- Nothing (yet)!
+### Changed
+
+- Updated the interface for `reactpy.hooks.use_channel_layer` to be more intuitive.
+ - Arguments now must be provided as keyworded arguments.
+ - The `name` argument has been renamed to `channel`.
+ - The `group_name` argument has been renamed to `group`.
+ - The `group_add` and `group_discard` arguments have been removed for simplicity.
### [5.2.1] - 2025-01-10
diff --git a/README.md b/README.md
index f60e7a2d..3ebd3faf 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
# ReactPy-Django
-
-
+
+
diff --git a/docs/examples/python/use_channel_layer.py b/docs/examples/python/use_channel_layer.py
index f504c978..ff9330a4 100644
--- a/docs/examples/python/use_channel_layer.py
+++ b/docs/examples/python/use_channel_layer.py
@@ -6,17 +6,17 @@
@component
def my_component():
async def receive_message(message):
- set_message(message["text"])
+ set_message_data(message["text"])
async def send_message(event):
if event["key"] == "Enter":
await sender({"text": event["target"]["value"]})
- message, set_message = hooks.use_state("")
- sender = use_channel_layer("my-channel-name", receiver=receive_message)
+ message_data, set_message_data = hooks.use_state("")
+ sender = use_channel_layer(group="my-group-name", receiver=receive_message)
return html.div(
- f"Received: {message}",
+ f"Received: {message_data}",
html.br(),
"Send: ",
html.input({"type": "text", "onKeyDown": send_message}),
diff --git a/docs/examples/python/use_channel_layer_group.py b/docs/examples/python/use_channel_layer_group.py
index 4e6aaa83..4189b47c 100644
--- a/docs/examples/python/use_channel_layer_group.py
+++ b/docs/examples/python/use_channel_layer_group.py
@@ -5,7 +5,7 @@
@component
def my_sender_component():
- sender = use_channel_layer(group_name="my-group-name")
+ sender = use_channel_layer(group="my-group-name")
async def submit_event(event):
if event["key"] == "Enter":
@@ -21,10 +21,10 @@ async def submit_event(event):
def my_receiver_component_1():
message, set_message = hooks.use_state("")
- async def receive_event(message):
+ async def receive_message(message):
set_message(message["text"])
- use_channel_layer(group_name="my-group-name", receiver=receive_event)
+ use_channel_layer(group="my-group-name", receiver=receive_message)
return html.div(f"Message Receiver 1: {message}")
@@ -33,9 +33,9 @@ async def receive_event(message):
def my_receiver_component_2():
message, set_message = hooks.use_state("")
- async def receive_event(message):
+ async def receive_message(message):
set_message(message["text"])
- use_channel_layer(group_name="my-group-name", receiver=receive_event)
+ use_channel_layer(group="my-group-name", receiver=receive_message)
return html.div(f"Message Receiver 2: {message}")
diff --git a/docs/examples/python/use_channel_layer_signal_receiver.py b/docs/examples/python/use_channel_layer_signal_receiver.py
index bd8c47f9..d858df3f 100644
--- a/docs/examples/python/use_channel_layer_signal_receiver.py
+++ b/docs/examples/python/use_channel_layer_signal_receiver.py
@@ -7,9 +7,10 @@
def my_receiver_component():
message, set_message = hooks.use_state("")
- async def receive_event(message):
+ async def receive_message(message):
set_message(message["text"])
- use_channel_layer("my-channel-name", receiver=receive_event)
+ # This is defined to receive any messages from both "my-channel-name" and "my-group-name".
+ use_channel_layer(channel="my-channel-name", group="my-group-name", receiver=receive_message)
return html.div(f"Message Receiver: {message}")
diff --git a/docs/examples/python/use_channel_layer_signal_sender.py b/docs/examples/python/use_channel_layer_signal_sender.py
index a35a6c88..b900bd2c 100644
--- a/docs/examples/python/use_channel_layer_signal_sender.py
+++ b/docs/examples/python/use_channel_layer_signal_sender.py
@@ -12,8 +12,10 @@ class ExampleModel(Model): ...
def my_sender_signal(sender, instance, **kwargs):
layer = get_channel_layer()
- # Example of sending a message to a channel
- async_to_sync(layer.send)("my-channel-name", {"text": "Hello World!"})
-
- # Example of sending a message to a group channel
+ # EXAMPLE 1: Sending a message to a group.
+ # Note that `group_send` requires using the `group` argument in `use_channel_layer`.
async_to_sync(layer.group_send)("my-group-name", {"text": "Hello World!"})
+
+ # EXAMPLE 2: Sending a message to a single channel.
+ # Note that this is typically only used for channels that use point-to-point communication
+ async_to_sync(layer.send)("my-channel-name", {"text": "Hello World!"})
diff --git a/docs/examples/python/use_channel_layer_single.py b/docs/examples/python/use_channel_layer_single.py
new file mode 100644
index 00000000..394cbc48
--- /dev/null
+++ b/docs/examples/python/use_channel_layer_single.py
@@ -0,0 +1,29 @@
+from reactpy import component, hooks, html
+
+from reactpy_django.hooks import use_channel_layer
+
+
+@component
+def my_sender_component():
+ sender = use_channel_layer(channel="my-channel-name")
+
+ async def submit_event(event):
+ if event["key"] == "Enter":
+ await sender({"text": event["target"]["value"]})
+
+ return html.div(
+ "Message Sender: ",
+ html.input({"type": "text", "onKeyDown": submit_event}),
+ )
+
+
+@component
+def my_receiver_component():
+ message, set_message = hooks.use_state("")
+
+ async def receive_message(message):
+ set_message(message["text"])
+
+ use_channel_layer(channel="my-channel-name", receiver=receive_message)
+
+ return html.div(f"Message Receiver 1: {message}")
diff --git a/docs/src/about/contributing.md b/docs/src/about/contributing.md
index b78a9508..06499c3e 100644
--- a/docs/src/about/contributing.md
+++ b/docs/src/about/contributing.md
@@ -50,7 +50,7 @@ By utilizing `hatch`, the following commands are available to manage the develop
The `hatch test` command is a wrapper for `pytest`. Hatch "intercepts" a handful of arguments, which can be previewed by typing `hatch test --help`.
- Any additional arguments in the `test` command are directly passed on to pytest. See the [pytest documentation](https://docs.pytest.org/en/stable/reference/reference.html#command-line-flags) for what additional arguments are available.
+ Any additional arguments in the `test` command are provided directly to pytest. See the [pytest documentation](https://docs.pytest.org/en/stable/reference/reference.html#command-line-flags) for what additional arguments are available.
### Linting and Formatting
diff --git a/docs/src/learn/your-first-component.md b/docs/src/learn/your-first-component.md
index 11e29798..e3be5da4 100644
--- a/docs/src/learn/your-first-component.md
+++ b/docs/src/learn/your-first-component.md
@@ -68,7 +68,7 @@ Additionally, you can pass in `#!python args` and `#!python kwargs` into your co
???+ tip "Components are automatically registered!"
- ReactPy-Django will automatically register any component that is referenced in a Django HTML template. This means you [typically](../reference/utils.md#register-component) do not need to manually register components in your **Django app**.
+ ReactPy-Django will automatically register any component that is referenced in a Django HTML template. This means you typically do not need to [manually register](../reference/utils.md#register-component) components in your **Django app**.
Please note that this HTML template must be properly stored within a registered Django app. ReactPy-Django will output a console log message containing all detected components when the server starts up.
diff --git a/docs/src/reference/components.md b/docs/src/reference/components.md
index 26feda67..448af463 100644
--- a/docs/src/reference/components.md
+++ b/docs/src/reference/components.md
@@ -58,6 +58,8 @@ This allows you to embedded any number of client-side PyScript components within
+{% include-markdown "./template-tag.md" start="" end="" %}
+
{% include-markdown "./template-tag.md" start="" end="" %}
{% include-markdown "./template-tag.md" start="" end="" trailing-newlines=false preserve-includer-indent=false %}
diff --git a/docs/src/reference/hooks.md b/docs/src/reference/hooks.md
index 89ce805c..45a77602 100644
--- a/docs/src/reference/hooks.md
+++ b/docs/src/reference/hooks.md
@@ -309,13 +309,13 @@ This hook utilizes the Django's authentication framework in a way that provides
The `#!python channels.auth.*` functions cannot trigger re-renders of your ReactPy components. Additionally, they do not provide persistent authentication when used within ReactPy.
- Django's authentication design requires cookies to retain login status. ReactPy is rendered via WebSockets, and browsers do not allow active WebSocket connections to modify cookies.
+ This is a result of Django's authentication design, which requires cookies to retain login status. ReactPy is rendered via WebSockets, and browsers do not allow active WebSocket connections to modify cookies.
To work around this limitation, when `#!python use_auth().login()` is called within your application, ReactPy performs the following process...
- 1. The server authenticates the user into the WebSocket session
- 2. The server generates a temporary login token linked to the WebSocket session
- 3. The server commands the browser to fetch the login token via HTTP
+ 1. The server authenticates the user into the WebSocket
+ 2. The server generates a temporary login token
+ 3. The server commands the browser to use the login token (via HTTP)
4. The client performs the HTTP request
5. The server returns the HTTP response, which contains all necessary cookies
6. The client stores these cookies in the browser
@@ -352,7 +352,7 @@ Shortcut that returns the WebSocket or HTTP connection's `#!python User`.
Store or retrieve a `#!python dict` containing arbitrary data specific to the connection's `#!python User`.
-This hook is useful for storing user-specific data, such as preferences, settings, or any generic key-value pairs.
+This hook is useful for storing user-specific data, such as preferences or settings.
User data saved with this hook is stored within the `#!python REACTPY_DATABASE`.
@@ -397,7 +397,7 @@ User data saved with this hook is stored within the `#!python REACTPY_DATABASE`.
### Use Channel Layer
-Subscribe to a [Django Channels layer](https://channels.readthedocs.io/en/latest/topics/channel_layers.html) to send/receive messages.
+Subscribe to a [Django Channels Layer](https://channels.readthedocs.io/en/latest/topics/channel_layers.html) to communicate messages.
Layers are a multiprocessing-safe communication system that allows you to send/receive messages between different parts of your application.
@@ -415,18 +415,16 @@ This is often used to create chat systems, synchronize data between components,
| Name | Type | Description | Default |
| --- | --- | --- | --- |
- | `#!python name` | `#!python str | None` | The name of the channel to subscribe to. If you define a `#!python group_name`, you can keep `#!python name` undefined to auto-generate a unique name. | `#!python None` |
- | `#!python group_name` | `#!python str | None` | If configured, any messages sent within this hook will be broadcasted to all channels in this group. | `#!python None` |
- | `#!python group_add` | `#!python bool` | If `#!python True`, the channel will automatically be added to the group when the component mounts. | `#!python True` |
- | `#!python group_discard` | `#!python bool` | If `#!python True`, the channel will automatically be removed from the group when the component dismounts. | `#!python True` |
- | `#!python receiver` | `#!python AsyncMessageReceiver | None` | An async function that receives a `#!python message: dict` from a channel. If more than one receiver waits on the same channel name, a random receiver will get the result. | `#!python None` |
- | `#!python layer` | `#!python str` | The channel layer to use. This layer must be defined in `#!python settings.py:CHANNEL_LAYERS`. | `#!python 'default'` |
+ | `#!python channel` | `#!python str | None` | The name of the channel this hook will send/receive messages on. If `#!python group` is defined and `#!python channel` is `#!python None`, ReactPy will automatically generate a unique channel name. | `#!python None` |
+ | `#!python group` | `#!python str | None` | If configured, the `#!python channel` is added to a `#!python group` and any messages sent by `#!python AsyncMessageSender` is broadcasted to all channels within the `#!python group`. | `#!python None` |
+ | `#!python receiver` | `#!python AsyncMessageReceiver | None` | An async function that receives a `#!python message: dict` from a channel. | `#!python None` |
+ | `#!python layer` | `#!python str` | The Django Channels layer to use. This layer must be defined in `settings.py:CHANNEL_LAYERS`. | `#!python 'default'` |
**Returns**
| Type | Description |
| --- | --- |
- | `#!python AsyncMessageSender` | An async callable that can send a `#!python message: dict`. |
+ | `#!python AsyncMessageSender` | An async callable that can send messages to the channel(s). This callable accepts a single argument, `#!python message: dict`, which is the data sent to the channel or group of channels. |
??? warning "Extra Django configuration required"
@@ -434,11 +432,11 @@ This is often used to create chat systems, synchronize data between components,
The [Django Channels documentation](https://channels.readthedocs.io/en/latest/topics/channel_layers.html#configuration) has information on what steps you need to take.
- In summary, you will need to:
+ Here is a short summary of the most common installation steps:
1. Install [`redis`](https://redis.io/download/) on your machine.
- 2. Run the following command to install `channels-redis` in your Python environment.
+ 2. Install `channels-redis` in your Python environment.
```bash linenums="0"
pip install channels-redis
@@ -457,13 +455,31 @@ This is often used to create chat systems, synchronize data between components,
}
```
-??? question "How do I broadcast a message to multiple components?"
+??? tip "Learn about the quirks of Django Channel Layers"
+
+ ReactPy tries to simplify the process of using Django Channels Layers, but it is important to understand how they work.
+
+ There are a few quirks of Django Channels Layers to be aware of:
+
+ - Any given `#!python channel` should only have one `#!python receiver` registered to it, under normal circumstances.
+ - This is why ReactPy automatically generates a unique channel name when using `#!python group`.
+ - When using `#!python group` within this hook, it is suggested to leave `#!python channel` undefined to let ReactPy automatically create a unique channel name (unless you know what you are doing).
+ - If you have multiple receivers for the same `#!python channel`, only one receiver will get the result.
+ - This quirk extends to groups as well. For example, If you have two component instances that use the same `#!python channel` within a `#!python group`, the message will only reach one receiver (for that channel).
+ - Channels exist independently of their `#!python group`.
+ - Groups are just a loose collection of channel names where a copy of each message can be sent.
+ - As a result, Django allows you to send messages directly to a `#!python channel` even if it is within a `#!python group`.
+ - By default, `#!python RedisChannelLayer` will close groups once they have existed for more than 24 hours.
+ - You need to create your own subclass of `#!python RedisChannelLayer` to change this behavior.
+ - By default, `#!python RedisChannelLayer` only allows 100 messages backlogged within a `#!python channel` receive queue.
+ - Rapidly sending messages can overwhelm this queue, resulting in new messages being dropped.
+ - If you expect to exceed this limit, you need to create your own subclass of `#!python RedisChannelLayer` to change this behavior.
- If more than one receiver waits on the same channel, a random one will get the result.
+??? question "How do I broadcast a message to multiple components?"
- To get around this, you can define a `#!python group_name` to broadcast messages to all channels within a specific group. If you do not define a channel `#!python name` while using groups, ReactPy will automatically generate a unique channel name for you.
+ Groups allow you to broadcast messages to all channels within that group. If you do not define a `#!python channel` while using groups, ReactPy will automatically generate a unique channel name for you.
- In the example below, all messages sent by the `#!python sender` component will be received by all `#!python receiver` components that exist (across every active client browser).
+ In the example below, since all components use the same channel `#!python group`, messages sent by `#!python my_sender_component` will reach all existing instances of `#!python my_receiver_component_1` and `#!python my_receiver_component_2`.
=== "components.py"
@@ -471,9 +487,25 @@ This is often used to create chat systems, synchronize data between components,
{% include "../../examples/python/use_channel_layer_group.py" %}
```
+??? question "How do I send a message to a single component (point-to-point communication)?"
+
+ The most common way of using `#!python use_channel_layer` is to broadcast messages to multiple components via a `#!python group`.
+
+ However, you can also use this hook to establish unidirectional, point-to-point communication towards a single `#!python receiver` function. This is slightly more efficient since it avoids the overhead of `#!python group` broadcasting.
+
+ In the example below, `#!python my_sender_component` will communicate directly to a single instance of `#!python my_receiver_component`. This is achieved by defining a `#!python channel` while omitting the `#!python group` parameter.
+
+ === "components.py"
+
+ ```python
+ {% include "../../examples/python/use_channel_layer_single.py" %}
+ ```
+
+ Note that if you have multiple instances of `#!python my_receiver_component` using the same `#!python channel`, only one will receive the message.
+
??? question "How do I signal a re-render from something that isn't a component?"
- There are occasions where you may want to signal a re-render from something that isn't a component, such as a Django model signal.
+ There are occasions where you may want to signal to the `#!python use_channel_layer` hook from something that isn't a component, such as a Django [model signal](https://docs.djangoproject.com/en/stable/topics/signals/).
In these cases, you can use the `#!python use_channel_layer` hook to receive a signal within your component, and then use the `#!python get_channel_layer().send(...)` to send the signal.
@@ -499,7 +531,7 @@ This is often used to create chat systems, synchronize data between components,
### Use Connection
-Returns the active connection, which is either a Django [WebSocket](https://channels.readthedocs.io/en/stable/topics/consumers.html#asyncjsonwebsocketconsumer) or a [HTTP Request](https://docs.djangoproject.com/en/4.2/ref/request-response/#django.http.HttpRequest).
+Returns the active connection, which is either a Django [WebSocket](https://channels.readthedocs.io/en/stable/topics/consumers.html#asyncjsonwebsocketconsumer) or a [HTTP Request](https://docs.djangoproject.com/en/stable/ref/request-response/#django.http.HttpRequest).
=== "components.py"
@@ -601,7 +633,7 @@ Shortcut that returns the root component's `#!python id` from the WebSocket or H
The root ID is a randomly generated `#!python uuid4`. It is notable to mention that it is persistent across the current connection. The `uuid` is reset only when the page is refreshed.
-This is useful when used in combination with [`#!python use_channel_layer`](#use-channel-layer) to send messages to a specific component instance, and/or retain a backlog of messages in case that component is disconnected via `#!python use_channel_layer( ... , group_discard=False)`.
+This is useful when used in combination with [`#!python use_channel_layer`](#use-channel-layer) to send messages to a specific component instance.
=== "components.py"
diff --git a/docs/src/reference/template-tag.md b/docs/src/reference/template-tag.md
index f41eaf44..952d3571 100644
--- a/docs/src/reference/template-tag.md
+++ b/docs/src/reference/template-tag.md
@@ -46,11 +46,11 @@ Each component loaded via this template tag will receive a dedicated WebSocket c
=== "my_template.html"
```jinja
-
- {% component "example_project.my_app.components.hello_world" recipient="World" %}
-
{% component my_variable recipient="World" %}
+
+
+ {% component "example_project.my_app.components.hello_world" recipient="World" %}
```
=== "views.py"
@@ -194,6 +194,19 @@ The entire file path provided is loaded directly into the browser, and must have
| `#!python initial` | `#!python str | VdomDict | ComponentType` | The initial HTML that is displayed prior to the PyScript component loads. This can either be a string containing raw HTML, a `#!python reactpy.html` snippet, or a non-interactive component. | `#!python ""` |
| `#!python root` | `#!python str` | The name of the root component function. | `#!python "root"` |
+
+
+??? tip "Get PyScript type hints within your editor via `webtypy`!"
+
+ By installing the `webtypy` package, you can get type hints for PyScript components within your editor.
+
+ !!! example "Terminal"
+
+ ```bash linenums="0"
+ pip install webtypy
+ ```
+
+
??? question "How do I execute JavaScript within PyScript components?"
diff --git a/pyproject.toml b/pyproject.toml
index 2354e3a5..64d530e7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -75,7 +75,7 @@ installer = "uv"
[[tool.hatch.build.hooks.build-scripts.scripts]]
commands = [
"bun install --cwd src/js",
- "bun build src/js/src/index.ts --outfile src/reactpy_django/static/reactpy_django/client.js --minify",
+ 'bun build src/js/src/index.ts --outdir="src/reactpy_django/static/reactpy_django/" --minify --sourcemap=linked',
'cd src/build_scripts && python copy_dir.py "src/js/node_modules/@pyscript/core/dist" "src/reactpy_django/static/reactpy_django/pyscript"',
'cd src/build_scripts && python copy_dir.py "src/js/node_modules/morphdom/dist" "src/reactpy_django/static/reactpy_django/morphdom"',
]
@@ -133,6 +133,7 @@ addopts = """\
--strict-config
--strict-markers
--reuse-db
+ --maxfail=10
"""
django_find_project = false
DJANGO_SETTINGS_MODULE = "test_app.settings_single_db"
diff --git a/src/js/src/client.ts b/src/js/src/client.ts
index a856fb3a..1f506f56 100644
--- a/src/js/src/client.ts
+++ b/src/js/src/client.ts
@@ -19,11 +19,14 @@ export class ReactPyDjangoClient
constructor(props: ReactPyDjangoClientProps) {
super();
this.urls = props.urls;
+ this.mountElement = props.mountElement;
+ this.prerenderElement = props.prerenderElement;
+ this.offlineElement = props.offlineElement;
this.socket = createReconnectingWebSocket({
- readyPromise: this.ready,
url: this.urls.componentUrl,
- onMessage: async ({ data }) => this.handleIncoming(JSON.parse(data)),
+ readyPromise: this.ready,
...props.reconnectOptions,
+ onMessage: async ({ data }) => this.handleIncoming(JSON.parse(data)),
onClose: () => {
// If offlineElement exists, show it and hide the mountElement/prerenderElement
if (this.prerenderElement) {
@@ -43,9 +46,6 @@ export class ReactPyDjangoClient
}
},
});
- this.mountElement = props.mountElement;
- this.prerenderElement = props.prerenderElement;
- this.offlineElement = props.offlineElement;
}
sendMessage(message: any): void {
diff --git a/src/js/src/mount.tsx b/src/js/src/mount.tsx
index 81115f9e..a3a02087 100644
--- a/src/js/src/mount.tsx
+++ b/src/js/src/mount.tsx
@@ -52,7 +52,6 @@ export function mountComponent(
const client = new ReactPyDjangoClient({
urls: {
componentUrl: componentUrl,
- query: document.location.search,
jsModules: `${httpOrigin}/${jsModulesPath}`,
},
reconnectOptions: {
diff --git a/src/js/src/types.ts b/src/js/src/types.ts
index 1f0e2b23..e3be73d7 100644
--- a/src/js/src/types.ts
+++ b/src/js/src/types.ts
@@ -7,7 +7,6 @@ export type ReconnectOptions = {
export type ReactPyUrls = {
componentUrl: URL;
- query: string;
jsModules: string;
};
diff --git a/src/reactpy_django/checks.py b/src/reactpy_django/checks.py
index 32f38768..88001c77 100644
--- a/src/reactpy_django/checks.py
+++ b/src/reactpy_django/checks.py
@@ -62,11 +62,11 @@ def reactpy_warnings(app_configs, **kwargs):
)
)
- # Check if reactpy_django/client.js is available
- if not find("reactpy_django/client.js"):
+ # Check if reactpy_django/index.js is available
+ if not find("reactpy_django/index.js"):
warnings.append(
Warning(
- "ReactPy client.js could not be found within Django static files!",
+ "ReactPy index.js could not be found within Django static files!",
hint="Check all static files related Django settings and INSTALLED_APPS.",
id="reactpy_django.W004",
)
diff --git a/src/reactpy_django/components.py b/src/reactpy_django/components.py
index f2ca561c..9234c42e 100644
--- a/src/reactpy_django/components.py
+++ b/src/reactpy_django/components.py
@@ -14,7 +14,13 @@
from reactpy_django.exceptions import ViewNotRegisteredError
from reactpy_django.forms.components import _django_form
from reactpy_django.pyscript.components import _pyscript_component
-from reactpy_django.utils import cached_static_file, generate_obj_name, import_module, render_view
+from reactpy_django.utils import (
+ cached_static_file,
+ del_html_head_body_transform,
+ generate_obj_name,
+ import_module,
+ render_view,
+)
if TYPE_CHECKING:
from collections.abc import Sequence
@@ -224,7 +230,7 @@ async def _render_view():
set_converted_view(
utils.html_to_vdom(
response.content.decode("utf-8").strip(),
- utils.del_html_head_body_transform,
+ del_html_head_body_transform,
*transforms,
strict=strict_parsing,
)
diff --git a/src/reactpy_django/forms/components.py b/src/reactpy_django/forms/components.py
index 9aa99497..a10d5c92 100644
--- a/src/reactpy_django/forms/components.py
+++ b/src/reactpy_django/forms/components.py
@@ -27,7 +27,7 @@
from reactpy.core.types import VdomDict
DjangoForm = export(
- module_from_file("reactpy-django", file=Path(__file__).parent.parent / "static" / "reactpy_django" / "client.js"),
+ module_from_file("reactpy-django", file=Path(__file__).parent.parent / "static" / "reactpy_django" / "index.js"),
("DjangoForm"),
)
diff --git a/src/reactpy_django/hooks.py b/src/reactpy_django/hooks.py
index 6ad3e7d6..de18c190 100644
--- a/src/reactpy_django/hooks.py
+++ b/src/reactpy_django/hooks.py
@@ -340,11 +340,9 @@ async def _set_user_data(data: dict):
def use_channel_layer(
- name: str | None = None,
*,
- group_name: str | None = None,
- group_add: bool = True,
- group_discard: bool = True,
+ channel: str | None = None,
+ group: str | None = None,
receiver: AsyncMessageReceiver | None = None,
layer: str = DEFAULT_CHANNEL_LAYER,
) -> AsyncMessageSender:
@@ -352,25 +350,24 @@ def use_channel_layer(
Subscribe to a Django Channels layer to send/receive messages.
Args:
- name: The name of the channel to subscribe to. If you define a `group_name`, you \
- can keep `name` undefined to auto-generate a unique name.
- group_name: If configured, any messages sent within this hook will be broadcasted \
- to all channels in this group.
- group_add: If `True`, the channel will automatically be added to the group \
- when the component mounts.
- group_discard: If `True`, the channel will automatically be removed from the \
- group when the component dismounts.
- receiver: An async function that receives a `message: dict` from a channel. \
- If more than one receiver waits on the same channel name, a random receiver \
- will get the result.
- layer: The channel layer to use. This layer must be defined in \
- `settings.py:CHANNEL_LAYERS`.
+ channel: The name of the channel this hook will send/receive messages on. If `group` is \
+ defined and `channel` is `None`, ReactPy will automatically generate a unique channel name.
+
+ Kwargs:
+ group: If configured, the `channel` is added to a `group` and any messages sent by `AsyncMessageSender` \
+ is broadcasted to all channels within the `group`.
+ receiver: An async function that receives a `message: dict` from a channel.
+ layer: The Django Channels layer to use. This layer must be defined in `settings.py:CHANNEL_LAYERS`.
+
+ Returns:
+ An async callable that can send messages to the channel(s). This callable accepts a single \
+ argument, `message: dict`, which is the data sent to the channel or group of channels.
"""
channel_layer: InMemoryChannelLayer | RedisChannelLayer = get_channel_layer(layer) # type: ignore
- channel_name = use_memo(lambda: str(name or uuid4()))
+ channel_name = use_memo(lambda: str(channel or uuid4()))
- if not name and not group_name:
- msg = "You must define a `name` or `group_name` for the channel."
+ if not (channel or group):
+ msg = "You must either define a `channel` or `group` for this hook."
raise ValueError(msg)
if not channel_layer:
@@ -383,11 +380,9 @@ def use_channel_layer(
# Add/remove a group's channel during component mount/dismount respectively.
@use_effect(dependencies=[])
async def group_manager():
- if group_name and group_add:
- await channel_layer.group_add(group_name, channel_name)
-
- if group_name and group_discard:
- return lambda: asyncio.run(channel_layer.group_discard(group_name, channel_name))
+ if group:
+ await channel_layer.group_add(group, channel_name)
+ return lambda: asyncio.run(channel_layer.group_discard(group, channel_name))
return None
# Listen for messages on the channel using the provided `receiver` function.
@@ -402,8 +397,8 @@ async def message_receiver():
# User interface for sending messages to the channel
async def message_sender(message: dict):
- if group_name:
- await channel_layer.group_send(group_name, message)
+ if group:
+ await channel_layer.group_send(group, message)
else:
await channel_layer.send(channel_name, message)
diff --git a/src/reactpy_django/javascript_components.py b/src/reactpy_django/javascript_components.py
index eb4fa035..80072211 100644
--- a/src/reactpy_django/javascript_components.py
+++ b/src/reactpy_django/javascript_components.py
@@ -5,6 +5,6 @@
from reactpy import web
HttpRequest = web.export(
- web.module_from_file("reactpy-django", file=Path(__file__).parent / "static" / "reactpy_django" / "client.js"),
+ web.module_from_file("reactpy-django", file=Path(__file__).parent / "static" / "reactpy_django" / "index.js"),
("HttpRequest"),
)
diff --git a/src/reactpy_django/templates/reactpy/component.html b/src/reactpy_django/templates/reactpy/component.html
index 7e3746f5..b7c9d705 100644
--- a/src/reactpy_django/templates/reactpy/component.html
+++ b/src/reactpy_django/templates/reactpy/component.html
@@ -10,7 +10,7 @@
{% if reactpy_prerender_html %}