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",
),
)
6 changes: 1 addition & 5 deletions docs/examples/python/pyscript_tag.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from reactpy import component, html

from reactpy_django.html import pyscript

example_source_code = """
import js

Expand All @@ -11,6 +9,4 @@

@component
def server_side_component():
return html.div(
pyscript(example_source_code.strip()),
)
return html.py_script(example_source_code.strip())
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
10 changes: 4 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ 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",
"typing_extensions",
]
dynamic = ["version"]
Expand Down Expand Up @@ -96,18 +95,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
Binary file modified src/js/bun.lockb
Binary file not shown.
28 changes: 12 additions & 16 deletions src/js/package.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
{
"dependencies": {
"@pyscript/core": "^0.6",
"@reactpy/client": "^0.3.2",
"event-to-object": "^0.1.2",
"morphdom": "^2.7.4",
"preact": "^10.26.9",
"react": "npm:@preact/compat@17.1.2",
"react-dom": "npm:@preact/compat@17.1.2"
"@pyscript/core": "^0.7.17",
"@reactpy/client": "^1.0.3",
"morphdom": "^2.7.8"
},
"devDependencies": {
"@eslint/js": "^9.29.0",
"bun-types": "^0.5.0",
"eslint": "^9.13.0",
"globals": "^16.2.0",
"prettier": "^3.3.3",
"typescript": "^5.8.3",
"typescript-eslint": "^8.34.0"
"@eslint/js": "^10.0.1",
"bun-types": "^1.3.9",
"eslint": "^10.0.0",
"globals": "^17.3.0",
"prettier": "^3.8.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.55.0"
},
"license": "MIT",
"name": "@reactpy-django/app",
"scripts": {
"check": "prettier --check . && eslint",
"format": "prettier --write . && eslint --fix"
},
"type": "module"
}
}
43 changes: 15 additions & 28 deletions src/js/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
import {
BaseReactPyClient,
type ReactPyClient,
type ReactPyModule,
ReactPyClient,
createReconnectingWebSocket,
type GenericReactPyClientProps,
} from "@reactpy/client";
import { createReconnectingWebSocket } from "./utils";
import type { ReactPyDjangoClientProps, ReactPyUrls } from "./types";

export class ReactPyDjangoClient
extends BaseReactPyClient
implements ReactPyClient
{
urls: ReactPyUrls;
socket: { current?: WebSocket };
mountElement: HTMLElement | null = null;
export class ReactPyDjangoClient extends ReactPyClient {
prerenderElement: HTMLElement | null = null;
offlineElement: HTMLElement | null = null;

constructor(props: ReactPyDjangoClientProps) {
super();
this.urls = props.urls;
this.mountElement = props.mountElement;
this.prerenderElement = props.prerenderElement;
this.offlineElement = props.offlineElement;
constructor(props: GenericReactPyClientProps) {
super(props);
this.prerenderElement = document.getElementById(
props.mountElement.id + "-prerender",
);
this.offlineElement = document.getElementById(
props.mountElement.id + "-offline",
);
this.socket = createReconnectingWebSocket({
url: this.urls.componentUrl,
readyPromise: this.ready,
...props.reconnectOptions,
// onMessage: Use standard ReactPy message routing
onMessage: async ({ data }) => this.handleIncoming(JSON.parse(data)),
// onClose: If offlineElement exists, show it and hide the mountElement/prerenderElement
onClose: () => {
// If offlineElement exists, show it and hide the mountElement/prerenderElement
if (this.prerenderElement) {
this.prerenderElement.remove();
this.prerenderElement = null;
Expand All @@ -38,21 +33,13 @@ export class ReactPyDjangoClient
this.offlineElement.hidden = false;
}
},
// onOpen: If offlineElement exists, hide it and show the mountElement
onOpen: () => {
// If offlineElement exists, hide it and show the mountElement
if (this.offlineElement && this.mountElement) {
this.offlineElement.hidden = true;
this.mountElement.hidden = false;
}
},
});
}

sendMessage(message: any): void {
this.socket.current?.send(JSON.stringify(message));
}

loadModule(moduleName: string): Promise<ReactPyModule> {
return import(`${this.urls.jsModules}/${moduleName}`);
}
}
61 changes: 24 additions & 37 deletions src/js/src/components.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,11 @@
import { React } from "@reactpy/client";
import type { DjangoFormProps, HttpRequestProps } from "./types";
import { useEffect } from "preact/hooks";
import { type ComponentChildren, render, createElement } from "preact";
/**
* Interface used to bind a ReactPy node to React.
*/
export function bind(node: HTMLElement | Element | Node) {
return {
create: (
type: string,
props: Record<string, unknown>,
children: ComponentChildren[],
) => createElement(type, props, ...children),
render: (element: HTMLElement | Element | Node) => {
render(element, node);
},
unmount: () => render(null, node),
};
}

export function DjangoForm({
onSubmitCallback,
formId,
}: DjangoFormProps): null {
useEffect(() => {
export class DjangoForm extends React.Component<DjangoFormProps> {
componentDidMount() {
const { onSubmitCallback, formId } = this.props;
const form = document.getElementById(formId) as HTMLFormElement;

// Submission event function
const onSubmitEvent = (event: Event) => {
event.preventDefault();
const formData = new FormData(form);
Expand Down Expand Up @@ -53,24 +33,29 @@ export function DjangoForm({
onSubmitCallback(formDataObject);
};

// Bind the event listener
if (form) {
form.addEventListener("submit", onSubmitEvent);
// Store cleanup function in instance
(this as any)._cleanup = () => {
form.removeEventListener("submit", onSubmitEvent);
};
}
}

// Unbind the event listener when the component dismounts
return () => {
if (form) {
form.removeEventListener("submit", onSubmitEvent);
}
};
}, []);
componentWillUnmount() {
if ((this as any)._cleanup) {
(this as any)._cleanup();
}
}

return null;
render() {
return null;
}
}

export function HttpRequest({ method, url, body, callback }: HttpRequestProps) {
useEffect(() => {
export class HttpRequest extends React.Component<HttpRequestProps> {
componentDidMount() {
const { method, url, body, callback } = this.props;
fetch(url, {
method: method,
body: body,
Expand All @@ -88,7 +73,9 @@ export function HttpRequest({ method, url, body, callback }: HttpRequestProps) {
.catch(() => {
callback(520, "");
});
}, []);
}

return null;
render() {
return null;
}
}
2 changes: 1 addition & 1 deletion src/js/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { HttpRequest, DjangoForm, bind } from "./components";
export { HttpRequest, DjangoForm } from "./components";
export { mountComponent } from "./mount";
Loading