Skip to content

Commit 464b4e2

Browse files
authored
Refactoring for v5.2 (#273)
1 parent 5596d8d commit 464b4e2

File tree

33 files changed

+683
-524
lines changed

33 files changed

+683
-524
lines changed

.github/workflows/test-python.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,18 @@ jobs:
4646
run: pip install --upgrade pip hatch uv
4747
- name: Check Python formatting
4848
run: hatch fmt src tests --check
49+
50+
python-types:
51+
runs-on: ubuntu-latest
52+
steps:
53+
- uses: actions/checkout@v4
54+
- uses: oven-sh/setup-bun@v2
55+
with:
56+
bun-version: latest
57+
- uses: actions/setup-python@v5
58+
with:
59+
python-version: 3.x
60+
- name: Install Python Dependencies
61+
run: pip install --upgrade pip hatch uv
62+
- name: Check Python formatting
63+
run: hatch run python:type_check

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ Don't forget to remove deprecated code on each major release!
2323

2424
- Automatically convert Django forms to ReactPy forms via the new `reactpy_django.components.django_form` component!
2525

26+
### Changed
27+
28+
- Refactoring of internal code to improve maintainability. No changes to public/documented API.
29+
2630
## [5.1.1] - 2024-12-02
2731

2832
### Fixed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from django.db import models
2+
3+
4+
class TodoItem(models.Model): ...

docs/examples/python/pyscript_ffi.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from pyscript import document, window
2+
from reactpy import component, html
3+
4+
5+
@component
6+
def root():
7+
def on_click(event):
8+
my_element = document.querySelector("#example")
9+
my_element.innerText = window.location.hostname
10+
11+
return html.div(
12+
{"id": "example"},
13+
html.button({"onClick": on_click}, "Click Me!"),
14+
)

docs/src/about/contributing.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ By utilizing `hatch`, the following commands are available to manage the develop
6262
| `hatch fmt --formatter` | Run only formatters |
6363
| `hatch run javascript:check` | Run the JavaScript linter/formatter |
6464
| `hatch run javascript:fix` | Run the JavaScript linter/formatter and write fixes to disk |
65+
| `hatch run python:type_check` | Run the Python type checker |
6566

6667
??? tip "Configure your IDE for linting"
6768

docs/src/reference/template-tag.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,17 @@ The entire file path provided is loaded directly into the browser, and must have
214214
{% include "../../examples/python/pyodide_js_module.py" %}
215215
```
216216

217-
**PyScript FFI**
217+
**PyScript Foreign Function Interface (FFI)**
218218

219-
...
219+
PyScript FFI has similar functionality to Pyodide's `js` module, but utilizes a different API.
220+
221+
There are two importable modules available that are available within the FFI interface: `window` and `document`.
222+
223+
=== "root.py"
224+
225+
```python
226+
{% include "../../examples/python/pyscript_ffi.py" %}
227+
```
220228

221229
**PyScript JS Modules**
222230

pyproject.toml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ installer = "uv"
7575
[[tool.hatch.build.hooks.build-scripts.scripts]]
7676
commands = [
7777
"bun install --cwd src/js",
78-
"bun build src/js/src/index.tsx --outfile src/reactpy_django/static/reactpy_django/client.js --minify",
78+
"bun build src/js/src/index.ts --outfile src/reactpy_django/static/reactpy_django/client.js --minify",
7979
'cd src/build_scripts && python copy_dir.py "src/js/node_modules/@pyscript/core/dist" "src/reactpy_django/static/reactpy_django/pyscript"',
8080
'cd src/build_scripts && python copy_dir.py "src/js/node_modules/morphdom/dist" "src/reactpy_django/static/reactpy_django/morphdom"',
8181
]
@@ -95,6 +95,8 @@ extra-dependencies = [
9595
"tblib",
9696
"servestatic",
9797
"django-bootstrap5",
98+
"decorator",
99+
98100
]
99101
matrix-name-format = "{variable}-{value}"
100102

@@ -185,6 +187,16 @@ linkcheck = [
185187
deploy_latest = ["cd docs && mike deploy --push --update-aliases {args} latest"]
186188
deploy_develop = ["cd docs && mike deploy --push develop"]
187189

190+
################################
191+
# >>> Hatch Python Scripts <<< #
192+
################################
193+
194+
[tool.hatch.envs.python]
195+
extra-dependencies = ["django-stubs", "channels-redis", "pyright"]
196+
197+
[tool.hatch.envs.python.scripts]
198+
type_check = ["pyright src"]
199+
188200
############################
189201
# >>> Hatch JS Scripts <<< #
190202
############################

src/js/src/client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ export class ReactPyDjangoClient
3030
this.prerenderElement.remove();
3131
this.prerenderElement = null;
3232
}
33-
if (this.offlineElement) {
33+
if (this.offlineElement && this.mountElement) {
3434
this.mountElement.hidden = true;
3535
this.offlineElement.hidden = false;
3636
}
3737
},
3838
onOpen: () => {
3939
// If offlineElement exists, hide it and show the mountElement
40-
if (this.offlineElement) {
40+
if (this.offlineElement && this.mountElement) {
4141
this.offlineElement.hidden = true;
4242
this.mountElement.hidden = false;
4343
}

src/js/src/components.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { DjangoFormProps } from "./types";
2+
import React from "react";
3+
import ReactDOM from "react-dom";
4+
/**
5+
* Interface used to bind a ReactPy node to React.
6+
*/
7+
export function bind(node) {
8+
return {
9+
create: (type, props, children) =>
10+
React.createElement(type, props, ...children),
11+
render: (element) => {
12+
ReactDOM.render(element, node);
13+
},
14+
unmount: () => ReactDOM.unmountComponentAtNode(node),
15+
};
16+
}
17+
18+
export function DjangoForm({
19+
onSubmitCallback,
20+
formId,
21+
}: DjangoFormProps): null {
22+
React.useEffect(() => {
23+
const form = document.getElementById(formId) as HTMLFormElement;
24+
25+
// Submission event function
26+
const onSubmitEvent = (event) => {
27+
event.preventDefault();
28+
const formData = new FormData(form);
29+
30+
// Convert the FormData object to a plain object by iterating through it
31+
// If duplicate keys are present, convert the value into an array of values
32+
const entries = formData.entries();
33+
const formDataArray = Array.from(entries);
34+
const formDataObject = formDataArray.reduce((acc, [key, value]) => {
35+
if (acc[key]) {
36+
if (Array.isArray(acc[key])) {
37+
acc[key].push(value);
38+
} else {
39+
acc[key] = [acc[key], value];
40+
}
41+
} else {
42+
acc[key] = value;
43+
}
44+
return acc;
45+
}, {});
46+
47+
onSubmitCallback(formDataObject);
48+
};
49+
50+
// Bind the event listener
51+
if (form) {
52+
form.addEventListener("submit", onSubmitEvent);
53+
}
54+
55+
// Unbind the event listener when the component dismounts
56+
return () => {
57+
if (form) {
58+
form.removeEventListener("submit", onSubmitEvent);
59+
}
60+
};
61+
}, []);
62+
63+
return null;
64+
}

src/js/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { DjangoForm, bind } from "./components";
2+
export { mountComponent } from "./mount";

0 commit comments

Comments
 (0)