Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions docs/examples/python/pyscript_component_initial_object.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from reactpy import component, html

from reactpy_django.components import pyscript_component
from reactpy import component, html, pyscript_component


@component
Expand Down
4 changes: 1 addition & 3 deletions docs/examples/python/pyscript_component_initial_string.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from reactpy import component, html

from reactpy_django.components import pyscript_component
from reactpy import component, html, pyscript_component


@component
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from reactpy import component, html

from reactpy_django.components import pyscript_component
from reactpy import component, html, pyscript_component


@component
Expand Down
4 changes: 1 addition & 3 deletions docs/examples/python/pyscript_component_root.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from reactpy import component, html

from reactpy_django.components import pyscript_component
from reactpy import component, html, pyscript_component


@component
Expand Down
8 changes: 4 additions & 4 deletions docs/examples/python/pyscript_ssr_parent.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from reactpy import component, html

from reactpy_django.components import pyscript_component
from reactpy import component, html, pyscript_component


@component
def server_side_component():
return html.div(
"This text is from my server-side component",
pyscript_component("./example_project/my_app/components/root.py"),
pyscript_component(
"./example_project/my_app/components/root.py",
),
)
2 changes: 1 addition & 1 deletion docs/examples/python/use_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
def my_component():
location = use_location()

return html.div(location.pathname + location.search)
return html.div(location.path + location.query_string)
2 changes: 1 addition & 1 deletion docs/overrides/homepage_examples/add_interactivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def searchable_video_list(videos):
search_text, set_search_text = use_state("")
found_videos = filter_videos(videos, search_text)

return html._(
return html(
search_input(
{"onChange": lambda event: set_search_text(event["target"]["value"])},
value=search_text,
Expand Down
2 changes: 1 addition & 1 deletion docs/src/reference/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ This allows you to embedded any number of client-side PyScript components within
| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `#!python *file_paths` | `#!python str` | File path to your client-side component. If multiple paths are provided, the contents are automatically merged. | N/A |
| `#!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 initial` | `#!python str | VdomDict | Component` | 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"` |

<!--pyscript-setup-required-start-->
Expand Down
2 changes: 1 addition & 1 deletion docs/src/reference/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ Shortcut that returns the browser's current `#!python Location`.

| Type | Description |
| --- | --- |
| `#!python Location` | An object containing the current URL's `#!python pathname` and `#!python search` query. |
| `#!python Location` | An object containing the current URL's `#!python path` and `#!python query_string` query. |

---

Expand Down
2 changes: 1 addition & 1 deletion docs/src/reference/template-tag.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ Your Python component file will be directly loaded into the browser. It must hav
| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `#!python *file_paths` | `#!python str` | File path to your client-side component. If multiple paths are provided, the contents are automatically merged. | N/A |
| `#!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 initial` | `#!python str | VdomDict | Component` | 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"` |

<!--pyscript-webtypy-start-->
Expand Down
9 changes: 4 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ classifiers = [
dependencies = [
"channels>=4.0.0",
"django>=4.2.0",
"reactpy>=1.1.0, <2.0.0",
"reactpy-router>=1.0.3, <2.0.0",
"reactpy>=2.0.0b10, <3.0.0",
"reactpy-router>2.0.0",
"dill>=0.3.5",
"orjson>=3.6.0",
"nest_asyncio>=1.5.0",
Expand Down Expand Up @@ -96,18 +96,17 @@ extra-dependencies = [
"servestatic",
"django-bootstrap5",
"decorator",

]
matrix-name-format = "{variable}-{value}"

# Django 4.2
[[tool.hatch.envs.hatch-test.matrix]]
python = ["3.9", "3.10", "3.11", "3.12"]
python = ["3.11", "3.12"]
django = ["4.2"]

# Django 5.0
[[tool.hatch.envs.hatch-test.matrix]]
python = ["3.10", "3.11", "3.12"]
python = ["3.11", "3.12"]
django = ["5.0"]

# Django 5.1
Expand Down
6 changes: 3 additions & 3 deletions src/js/package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"dependencies": {
"@pyscript/core": "^0.6",
"@reactpy/client": "^0.3.2",
"event-to-object": "^0.1.2",
"@reactpy/client": "^1.0.3",
"event-to-object": "^2.0.0",
"morphdom": "^2.7.4",
"preact": "^10.26.9",
"preact": "^10.27.2",
"react": "npm:@preact/compat@17.1.2",
"react-dom": "npm:@preact/compat@17.1.2"
},
Expand Down
2 changes: 1 addition & 1 deletion src/reactpy_django/auth/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def setup_asgi_scope():
any relevant actions."""
scope["reactpy"]["synchronize_auth"] = synchronize_auth

@hooks.use_effect(dependencies=[sync_needed])
@hooks.use_async_effect(dependencies=[sync_needed])
async def synchronize_auth_watchdog():
"""Detect if the client has taken too long to request a auth session synchronization.

Expand Down
40 changes: 8 additions & 32 deletions src/reactpy_django/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@
from django.http import HttpRequest
from django.urls import reverse
from reactpy import component, hooks, html, utils
from reactpy.types import ComponentType, Key, VdomDict
from reactpy.types import Component, Key, VdomDict

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,
del_html_head_body_transform,
Expand Down Expand Up @@ -54,7 +53,7 @@ def constructor(
*args,
key: Key | None = None,
**kwargs,
) -> ComponentType:
) -> Component:
return _view_to_component(
view=view,
transforms=transforms,
Expand Down Expand Up @@ -84,13 +83,13 @@ def constructor(
*args,
key: Key | None = None,
**kwargs,
) -> ComponentType:
) -> Component:
return _view_to_iframe(view=view, extra_props=extra_props, args=args, kwargs=kwargs, key=key)

return constructor


def django_css(static_path: str, key: Key | None = None) -> ComponentType:
def django_css(static_path: str, key: Key | None = None) -> Component:
"""Fetches a CSS static file for use within ReactPy. This allows for deferred CSS loading.

Args:
Expand All @@ -103,7 +102,7 @@ def django_css(static_path: str, key: Key | None = None) -> ComponentType:
return _django_css(static_path=static_path, key=key)


def django_js(static_path: str, key: Key | None = None) -> ComponentType:
def django_js(static_path: str, key: Key | None = None) -> Component:
"""Fetches a JS static file for use within ReactPy. This allows for deferred JS loading.

Args:
Expand Down Expand Up @@ -131,7 +130,7 @@ def django_form(
top_children: Sequence[Any] = (),
bottom_children: Sequence[Any] = (),
key: Key | None = None,
) -> ComponentType:
) -> Component:
"""Converts a Django form to a ReactPy component.

Args:
Expand Down Expand Up @@ -174,29 +173,6 @@ def django_form(
)


def pyscript_component(
*file_paths: str,
initial: str | VdomDict | ComponentType = "",
root: str = "root",
) -> ComponentType:
"""
Args:
file_paths: File path to your client-side component. If multiple paths are \
provided, the contents are automatically merged.

Kwargs:
initial: 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.
root: The name of the root component function.
"""
return _pyscript_component(
*file_paths,
initial=initial,
root=root,
)


@component
def _view_to_component(
view: Callable | View | str,
Expand All @@ -217,7 +193,7 @@ def _view_to_component(
resolved_view: Callable = import_module(view) if isinstance(view, str) else view # type: ignore

# Render the view render within a hook
@hooks.use_effect(
@hooks.use_async_effect(
dependencies=[
json.dumps(vars(_request), default=generate_obj_name),
json.dumps([_args, _kwargs], default=generate_obj_name),
Expand All @@ -228,7 +204,7 @@ async def _render_view():
# Render the view
response = await render_view(resolved_view, _request, _args, _kwargs)
set_converted_view(
utils.html_to_vdom(
utils.string_to_reactpy(
response.content.decode("utf-8").strip(),
del_html_head_body_transform,
*transforms,
Expand Down
6 changes: 3 additions & 3 deletions src/reactpy_django/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
from django.core.cache import DEFAULT_CACHE_ALIAS
from django.db import DEFAULT_DB_ALIAS
from reactpy.config import REACTPY_ASYNC_RENDERING as _REACTPY_ASYNC_RENDERING
from reactpy.config import REACTPY_DEBUG_MODE as _REACTPY_DEBUG_MODE
from reactpy.config import REACTPY_DEBUG as _REACTPY_DEBUG

from reactpy_django.utils import import_dotted_path

if TYPE_CHECKING:
from django.views import View
from reactpy.core.types import ComponentConstructor
from reactpy.types import ComponentConstructor

from reactpy_django.types import (
AsyncPostprocessor,
Expand All @@ -27,7 +27,7 @@

# Configurable through Django settings.py
DJANGO_DEBUG = settings.DEBUG # Snapshot of Django's DEBUG setting
_REACTPY_DEBUG_MODE.set_current(settings.DEBUG)
_REACTPY_DEBUG.set_current(settings.DEBUG)
_REACTPY_ASYNC_RENDERING.set_current(getattr(settings, "REACTPY_ASYNC_RENDERING", _REACTPY_ASYNC_RENDERING.current))
REACTPY_URL_PREFIX: str = getattr(
settings,
Expand Down
2 changes: 1 addition & 1 deletion src/reactpy_django/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

if TYPE_CHECKING:
from django.contrib.auth.models import AbstractUser
from reactpy.core.types import ComponentConstructor
from reactpy.types import ComponentConstructor


def user_passes_test(
Expand Down
15 changes: 8 additions & 7 deletions src/reactpy_django/forms/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.forms import Form, ModelForm
from reactpy import component, hooks, html, utils
from reactpy.core.events import event
from reactpy.web import export, module_from_file
from reactpy.reactjs import component_from_file

from reactpy_django.forms.transforms import (
convert_html_props_to_reactjs,
Expand All @@ -24,11 +24,12 @@
if TYPE_CHECKING:
from collections.abc import Sequence

from reactpy.core.types import VdomDict
from reactpy.types import VdomDict

DjangoForm = export(
module_from_file("reactpy-django", file=Path(__file__).parent.parent / "static" / "reactpy_django" / "index.js"),
("DjangoForm"),
DjangoForm = component_from_file(
Path(__file__).parent.parent / "static" / "reactpy_django" / "index.js",
import_names=("DjangoForm"),
name="reactpy-django",
)


Expand Down Expand Up @@ -63,7 +64,7 @@ def _django_form(
)

# Validate and render the form
@hooks.use_effect(dependencies=[str(submitted_data)])
@hooks.use_async_effect(dependencies=[str(submitted_data)])
async def render_form():
"""Forms must be rendered in an async loop to allow database fields to execute."""
if submitted_data:
Expand Down Expand Up @@ -113,7 +114,7 @@ async def _on_change(_event):
},
DjangoForm({"onSubmitCallback": on_submit_callback, "formId": f"reactpy-{uuid}"}),
*top_children,
utils.html_to_vdom(
utils.string_to_reactpy(
rendered_form,
convert_html_props_to_reactjs,
convert_textarea_children_to_prop,
Expand Down
5 changes: 3 additions & 2 deletions src/reactpy_django/forms/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from reactpy.core.events import EventHandler, to_event_handler_function

if TYPE_CHECKING:
from reactpy.core.types import VdomDict
from reactpy.types import VdomDict


def convert_html_props_to_reactjs(vdom_tree: VdomDict) -> VdomDict:
Expand Down Expand Up @@ -81,7 +81,8 @@ def infer_key_from_attributes(vdom_tree: VdomDict) -> VdomDict:
key = attributes.get("name")

if key:
vdom_tree["key"] = key
attributes["key"] = key
vdom_tree["attributes"] = attributes

return vdom_tree

Expand Down
8 changes: 4 additions & 4 deletions src/reactpy_django/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from channels import DEFAULT_CHANNEL_LAYER
from channels import auth as channels_auth
from channels.layers import InMemoryChannelLayer, get_channel_layer
from reactpy import use_callback, use_effect, use_memo, use_ref, use_state
from reactpy import use_async_effect, use_callback, use_effect, use_memo, use_ref, use_state
from reactpy import use_connection as _use_connection
from reactpy import use_location as _use_location
from reactpy import use_scope as _use_scope
Expand All @@ -43,7 +43,7 @@

from channels_redis.core import RedisChannelLayer
from django.contrib.auth.models import AbstractUser
from reactpy.backend.types import Location
from reactpy.types import Location


_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -378,15 +378,15 @@ def use_channel_layer(
raise ValueError(msg)

# Add/remove a group's channel during component mount/dismount respectively.
@use_effect(dependencies=[])
@use_async_effect(dependencies=[])
async def group_manager():
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.
@use_effect
@use_async_effect
async def message_receiver():
if not receiver:
return
Expand Down
4 changes: 2 additions & 2 deletions src/reactpy_django/html.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from reactpy.core.vdom import make_vdom_constructor
from reactpy import Vdom

pyscript = make_vdom_constructor("py-script")
pyscript = Vdom("py-script")
Copy link
Contributor

@Archmonger Archmonger Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer needed. An update to pyscript has deprecated the py-script HTML tag. The correct syntax now uses a normal script tag.

For example: <script type='py'>{EXAMPLE_CONTENTS}</script>.

See pyscript_component_html in reactpy core as an example.

Loading