Skip to content

Commit 2d79831

Browse files
authored
Refactoring related to ReactPy v1.1.0 (#50)
1 parent 44f76d2 commit 2d79831

File tree

13 files changed

+169
-245
lines changed

13 files changed

+169
-245
lines changed

.github/workflows/test-python.yml

+15
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,18 @@ jobs:
8080
run: pip install --upgrade pip hatch uv
8181
- name: Check Python formatting
8282
run: hatch fmt src tests --check
83+
84+
python-types:
85+
runs-on: ubuntu-latest
86+
steps:
87+
- uses: actions/checkout@v4
88+
- uses: oven-sh/setup-bun@v2
89+
with:
90+
bun-version: latest
91+
- uses: actions/setup-python@v5
92+
with:
93+
python-version: 3.x
94+
- name: Install Python Dependencies
95+
run: pip install --upgrade pip hatch uv
96+
- name: Run Python type checker
97+
run: hatch run python:type_check

CHANGELOG.md

+7-19
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1010

1111
<!--
1212
Using the following categories, list your changes in this order:
13+
[Added, Changed, Deprecated, Removed, Fixed, Security]
1314
14-
### Added
15-
- for new features.
16-
17-
### Changed
18-
- for changes in existing functionality.
19-
20-
### Deprecated
21-
- for soon-to-be removed features.
22-
23-
### Removed
24-
- for removed features.
25-
26-
### Fixed
27-
- for bug fixes.
28-
29-
### Security
30-
- for vulnerability fixes.
31-
-->
15+
Don't forget to remove deprecated code on each major release!
16+
-->
3217

3318
<!--changelog-start-->
3419

3520
## [Unreleased]
3621

3722
### Changed
3823

39-
- Set upper limit on ReactPy version to `<2.0.0`.
24+
- Set maximum ReactPy version to `<2.0.0`.
25+
- Set minimum ReactPy version to `1.1.0`.
26+
- `link` element now calculates URL changes using the client.
27+
- Refactoring related to `reactpy>=1.1.0` changes.
4028

4129
### Fixed
4230

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# <img src="https://raw.githubusercontent.com/reactive-python/reactpy/main/branding/svg/reactpy-logo-square.svg" align="left" height="45"/> ReactPy Router
22

33
<p>
4-
<a href="https://github.com/reactive-python/reactpy-router/actions/workflows/test-src.yml">
5-
<img src="https://github.com/reactive-python/reactpy-router/actions/workflows/test-src.yml/badge.svg">
4+
<a href="https://github.com/reactive-python/reactpy-router/actions/workflows/test-python.yml">
5+
<img src="https://github.com/reactive-python/reactpy-router/actions/workflows/test-python.yml/badge.svg">
66
</a>
77
<a href="https://pypi.python.org/pypi/reactpy-router">
88
<img src="https://img.shields.io/pypi/v/reactpy-router.svg?label=PyPI">

docs/src/about/contributing.md

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ By utilizing `hatch`, the following commands are available to manage the develop
4343
| `hatch fmt --formatter` | Run only formatters |
4444
| `hatch run javascript:check` | Run the JavaScript linter/formatter |
4545
| `hatch run javascript:fix` | Run the JavaScript linter/formatter and write fixes to disk |
46+
| `hatch run python:type_check` | Run the Python type checker |
4647

4748
??? tip "Configure your IDE for linting"
4849

pyproject.toml

+12-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ classifiers = [
2828
"Environment :: Web Environment",
2929
"Typing :: Typed",
3030
]
31-
dependencies = ["reactpy>=1.0.0, <2.0.0", "typing_extensions"]
31+
dependencies = ["reactpy>=1.1.0, <2.0.0", "typing_extensions"]
3232
dynamic = ["version"]
3333
urls.Changelog = "https://reactive-python.github.io/reactpy-router/latest/about/changelog/"
3434
urls.Documentation = "https://reactive-python.github.io/reactpy-router/latest/"
@@ -53,7 +53,7 @@ installer = "uv"
5353
[[tool.hatch.build.hooks.build-scripts.scripts]]
5454
commands = [
5555
"bun install --cwd src/js",
56-
"bun build src/js/src/index.js --outfile src/reactpy_router/static/bundle.js --minify",
56+
"bun build src/js/src/index.ts --outfile src/reactpy_router/static/bundle.js --minify",
5757
]
5858
artifacts = []
5959

@@ -106,6 +106,16 @@ linkcheck = [
106106
deploy_latest = ["cd docs && mike deploy --push --update-aliases {args} latest"]
107107
deploy_develop = ["cd docs && mike deploy --push develop"]
108108

109+
################################
110+
# >>> Hatch Python Scripts <<< #
111+
################################
112+
113+
[tool.hatch.envs.python]
114+
extra-dependencies = ["pyright"]
115+
116+
[tool.hatch.envs.python.scripts]
117+
type_check = ["pyright src"]
118+
109119
############################
110120
# >>> Hatch JS Scripts <<< #
111121
############################

src/js/src/components.ts

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React from "preact/compat";
2+
import ReactDOM from "preact/compat";
3+
import { createLocationObject, pushState, replaceState } from "./utils";
4+
import { HistoryProps, LinkProps, NavigateProps } from "./types";
5+
6+
/**
7+
* Interface used to bind a ReactPy node to React.
8+
*/
9+
export function bind(node) {
10+
return {
11+
create: (type, props, children) =>
12+
React.createElement(type, props, ...children),
13+
render: (element) => {
14+
ReactDOM.render(element, node);
15+
},
16+
unmount: () => ReactDOM.unmountComponentAtNode(node),
17+
};
18+
}
19+
20+
/**
21+
* History component that captures browser "history go back" actions and notifies the server.
22+
*/
23+
export function History({ onHistoryChangeCallback }: HistoryProps): null {
24+
// Tell the server about history "popstate" events
25+
React.useEffect(() => {
26+
const listener = () => {
27+
onHistoryChangeCallback(createLocationObject());
28+
};
29+
30+
// Register the event listener
31+
window.addEventListener("popstate", listener);
32+
33+
// Delete the event listener when the component is unmounted
34+
return () => window.removeEventListener("popstate", listener);
35+
});
36+
37+
// Tell the server about the URL during the initial page load
38+
React.useEffect(() => {
39+
onHistoryChangeCallback(createLocationObject());
40+
return () => {};
41+
}, []);
42+
return null;
43+
}
44+
45+
/**
46+
* Link component that captures clicks on anchor links and notifies the server.
47+
*
48+
* This component is not the actual `<a>` link element. It is just an event
49+
* listener for ReactPy-Router's server-side link component.
50+
*/
51+
export function Link({ onClickCallback, linkClass }: LinkProps): null {
52+
React.useEffect(() => {
53+
// Event function that will tell the server about clicks
54+
const handleClick = (event: Event) => {
55+
let click_event = event as MouseEvent;
56+
if (!click_event.ctrlKey) {
57+
event.preventDefault();
58+
let to = (event.currentTarget as HTMLElement).getAttribute("href");
59+
pushState(to);
60+
onClickCallback(createLocationObject());
61+
}
62+
};
63+
64+
// Register the event listener
65+
let link = document.querySelector(`.${linkClass}`);
66+
if (link) {
67+
link.addEventListener("click", handleClick);
68+
} else {
69+
console.warn(`Link component with class name ${linkClass} not found.`);
70+
}
71+
72+
// Delete the event listener when the component is unmounted
73+
return () => {
74+
if (link) {
75+
link.removeEventListener("click", handleClick);
76+
}
77+
};
78+
});
79+
return null;
80+
}
81+
82+
/**
83+
* Client-side portion of the navigate component, that allows the server to command the client to change URLs.
84+
*/
85+
export function Navigate({
86+
onNavigateCallback,
87+
to,
88+
replace = false,
89+
}: NavigateProps): null {
90+
React.useEffect(() => {
91+
if (replace) {
92+
replaceState(to);
93+
} else {
94+
pushState(to);
95+
}
96+
onNavigateCallback(createLocationObject());
97+
return () => {};
98+
}, []);
99+
100+
return null;
101+
}

src/js/src/index.ts

+1-129
Original file line numberDiff line numberDiff line change
@@ -1,129 +1 @@
1-
import React from "preact/compat";
2-
import ReactDOM from "preact/compat";
3-
import { createLocationObject, pushState, replaceState } from "./utils";
4-
import {
5-
HistoryProps,
6-
LinkProps,
7-
NavigateProps,
8-
FirstLoadProps,
9-
} from "./types";
10-
11-
/**
12-
* Interface used to bind a ReactPy node to React.
13-
*/
14-
export function bind(node) {
15-
return {
16-
create: (type, props, children) =>
17-
React.createElement(type, props, ...children),
18-
render: (element) => {
19-
ReactDOM.render(element, node);
20-
},
21-
unmount: () => ReactDOM.unmountComponentAtNode(node),
22-
};
23-
}
24-
25-
/**
26-
* History component that captures browser "history go back" actions and notifies the server.
27-
*/
28-
export function History({ onHistoryChangeCallback }: HistoryProps): null {
29-
React.useEffect(() => {
30-
// Register a listener for the "popstate" event and send data back to the server using the `onHistoryChange` callback.
31-
const listener = () => {
32-
onHistoryChangeCallback(createLocationObject());
33-
};
34-
35-
// Register the event listener
36-
window.addEventListener("popstate", listener);
37-
38-
// Delete the event listener when the component is unmounted
39-
return () => window.removeEventListener("popstate", listener);
40-
});
41-
42-
// Tell the server about the URL during the initial page load
43-
// FIXME: This code is commented out since it currently runs every time any component
44-
// is mounted due to a ReactPy core rendering bug. `FirstLoad` component is used instead.
45-
// https://github.com/reactive-python/reactpy/pull/1224
46-
// React.useEffect(() => {
47-
// onHistoryChange({
48-
// pathname: window.location.pathname,
49-
// search: window.location.search,
50-
// });
51-
// return () => {};
52-
// }, []);
53-
return null;
54-
}
55-
56-
/**
57-
* Link component that captures clicks on anchor links and notifies the server.
58-
*
59-
* This component is not the actual `<a>` link element. It is just an event
60-
* listener for ReactPy-Router's server-side link component.
61-
*
62-
* @disabled This component is currently unused due to a ReactPy core rendering bug
63-
* which causes duplicate rendering (and thus duplicate event listeners).
64-
*/
65-
export function Link({ onClickCallback, linkClass }: LinkProps): null {
66-
React.useEffect(() => {
67-
// Event function that will tell the server about clicks
68-
const handleClick = (event: MouseEvent) => {
69-
event.preventDefault();
70-
let to = (event.target as HTMLElement).getAttribute("href");
71-
pushState(to);
72-
onClickCallback(createLocationObject());
73-
};
74-
75-
// Register the event listener
76-
let link = document.querySelector(`.${linkClass}`);
77-
if (link) {
78-
link.addEventListener("click", handleClick);
79-
} else {
80-
console.warn(`Link component with class name ${linkClass} not found.`);
81-
}
82-
83-
// Delete the event listener when the component is unmounted
84-
return () => {
85-
let link = document.querySelector(`.${linkClass}`);
86-
if (link) {
87-
link.removeEventListener("click", handleClick);
88-
}
89-
};
90-
});
91-
return null;
92-
}
93-
94-
/**
95-
* Client-side portion of the navigate component, that allows the server to command the client to change URLs.
96-
*/
97-
export function Navigate({
98-
onNavigateCallback,
99-
to,
100-
replace = false,
101-
}: NavigateProps): null {
102-
React.useEffect(() => {
103-
if (replace) {
104-
replaceState(to);
105-
} else {
106-
pushState(to);
107-
}
108-
onNavigateCallback(createLocationObject());
109-
return () => {};
110-
}, []);
111-
112-
return null;
113-
}
114-
115-
/**
116-
* FirstLoad component that captures the URL during the initial page load and notifies the server.
117-
*
118-
* FIXME: This component only exists because of a ReactPy core rendering bug, and should be removed when the bug
119-
* is fixed. In the future, all this logic should be handled by the `History` component.
120-
* https://github.com/reactive-python/reactpy/pull/1224
121-
*/
122-
export function FirstLoad({ onFirstLoadCallback }: FirstLoadProps): null {
123-
React.useEffect(() => {
124-
onFirstLoadCallback(createLocationObject());
125-
return () => {};
126-
}, []);
127-
128-
return null;
129-
}
1+
export { bind, History, Link, Navigate } from "./components";

src/js/src/types.ts

-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,3 @@ export interface NavigateProps {
1717
to: string;
1818
replace?: boolean;
1919
}
20-
21-
export interface FirstLoadProps {
22-
onFirstLoadCallback: (location: ReactPyLocation) => void;
23-
}

src/js/src/utils.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,18 @@ export function createLocationObject(): ReactPyLocation {
77
};
88
}
99

10-
export function pushState(to: string): void {
10+
export function pushState(to: any): void {
11+
if (typeof to !== "string") {
12+
console.error("pushState() requires a string argument.");
13+
return;
14+
}
1115
window.history.pushState(null, "", new URL(to, window.location.href));
1216
}
1317

14-
export function replaceState(to: string): void {
18+
export function replaceState(to: any): void {
19+
if (typeof to !== "string") {
20+
console.error("replaceState() requires a string argument.");
21+
return;
22+
}
1523
window.history.replaceState(null, "", new URL(to, window.location.href));
1624
}

0 commit comments

Comments
 (0)